Dependency Injection

(in AngularJS)

What is Dependency Injection?

Dependency injection is a software design pattern that implements inversion of control (IoC) for resolving dependencies.

In simple words, dependency injection means giving an object its instance variables when needed.

Dependency Injection - Why?

  • Decoupled code components
  • Organized & structured code
  • Testing

What are we going to do?

Build  a program that implements DI and launches a rocket into space!

To do so, we have to push a big red button with a lid.

function launchRocket() {
  console.log('Launching rocket at ' + new Date);
}
function BigRedButton() {
}

BigRedButton.prototype.openLid = function() {
  this._open = true;
};

BigRedButton.prototype.closeLid = function() {
  this._open = false;
};

BigRedButton.prototype.push = function() {
  if (this._open) {
    launchRocket();
  }
};
function spaceProgram() {
  var button = new BigRedButton();
  button.openLid();
  button.push();
  button.closeLid();
}

The rocket launcher

Show Time!

function launchRocket() {
  console.log('Launching rocket at ' + new Date);
}

function BigRedButton() {
}

BigRedButton.prototype.openLid = function() {
  this._open = true;
};

BigRedButton.prototype.closeLid = function() {
  this._open = false;
};

BigRedButton.prototype.push = function() {
  if (this._open) {
    launchRocket();
  }
};

function spaceProgram() {
  var button = new BigRedButton();
  button.openLid();
  button.push();
  button.closeLid();
}

spaceProgram();

it works!

but something feels wrong...

  • BigRedButton coupled with launchRocket
  • spaceProgram coupled with BigRedButton
function launchRocket() {
  console.log('Launching rocket at ' + new Date);
}



function BigRedButton() {
}

BigRedButton.prototype.openLid = function() {
  this._open = true;
};

BigRedButton.prototype.closeLid = function() {
  this._open = false;
};

BigRedButton.prototype.push = function() {
  if (this._open) {
    launchRocket();
  }
};



function spaceProgram() {
  var button = new BigRedButton();
  button.openLid();
  button.push();
  button.closeLid();
}
function BigRedButton(buttonAction) {
  this.action = buttonAction;
}

BigRedButton.prototype.openLid = function() {
  this._open = true;
};

BigRedButton.prototype.closeLid = function() {
  this._.open = false;
};

BigRedButton.prototype.push = function() {
  if (this._open) {
    this.action();
  }
};
function spaceProgram() {
  var button = new BigRedButton(launchRocket);
  button.openLid();
  button.push();
  button.closeLid();
}
function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

Show Time!

function launchRocket() {
  console.log('Launching rocket at ' + new Date);
}

function BigRedButton(buttonAction) {
  this.action = buttonAction;
}

BigRedButton.prototype.openLid = function() {
  this._open = true;
};

BigRedButton.prototype.closeLid = function() {
  this._open = false;
};

BigRedButton.prototype.push = function() {
  if (this._open) {
    this.action();
  }
};

function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

spaceProgram(new BigRedButton(launchRocket));

Great!

But what if we had larger application?

it's difficult to maintain a large dependency graph manually

:(

Injector

Injector

Where we register application components and ask to inject them wherever they might be needed

"Hi injector! here's something called 'button'.

It is just a simple value that you can give to anyone who needs it"

var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));
var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));
injector.invoke(spaceProgram);
function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

"I need it!"

function createInjector() {
 

  function constant(name, value) {

  }
 
  function invoke(fn) {
 
  }
 
  return {
    constant: constant,
    invoke: invoke
  };
}
function createInjector() {
  var cache = {};
 
  function constant(name, value) {
    cache[name] = value;
  }
 
  function invoke(fn) {
 
  }
 
  return {
    constant: constant,
    invoke: invoke
  };
}

How can we figure out fn's expected dependencies ?

Function.prototype.toString()

    spaceProgram.toString(); 

>   "function spaceProgram(button) {
>      button.openLid();
>      button.push();
>      button.closeLid();
>   }"
 spaceProgram
   .toString()
   .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1]
   .split(','); // => ['button']

^function

\s*

[^\(]*

 

\(

 

[^\)]* 

\)

The source code always begins with the text 'function'

It is followed by (optionally) some white space

Then there is (optionally) the function's name, which we define as a succession of

characters other than the opening parenthesis.

Then comes the argument list, which starts with an opening parenthesis.

The argument list is captured into a capturing group, because it is the part we're

interested in.

What we capture is a succession of characters other than the closing parenthesis.

Then we close the capturing group.

Finally we match the closing parenthesis.

What the Func?!

/^function\s*[^\(]*\(([^\)]*)\)/
function createInjector() {
  var cache = {};
 
  function constant(name, value) {
    cache[name] = value;
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
            .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];

    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });





  }
 
  return {
    constant: constant,
    invoke: invoke
  };
}
function createInjector() {
  var cache = {};
 
  function constant(name, value) {
    cache[name] = value;
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
            .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];

    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });

    var args = argNames.map(function(argName) {
      return cache[argName];
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    invoke: invoke
  };
}

Back to our Injector

Show Time!

// Framework

function createInjector() {

  var cache = {};

  function constant(name, value) {
    cache[name]= value;
  }

  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      return cache[argName];
    });
    return fn.apply(null, args);
  }

  return {
    constant: constant,
    invoke: invoke
  };
}

// App

function launchRocket() {
  console.log('Launching rocket at '+new Date());
}

function BigRedButton(buttonAction) {
  this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
  this._open = true;
};
BigRedButton.prototype.closeLid = function() {
  this._open = false;
};
BigRedButton.prototype.push = function() {
  if (this._open) {
    this.action();
  }
};

function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));
injector.invoke(spaceProgram);

Cool, but...

we still have to manually construct the dependency between the big red button and the launchRocket function

new BigRedButton(launchRocket)

Factories

Instead of giving the injector the constant value directly, let's give it a function that returns that value!

injector.factory('button', function(buttonAction) {
  return new BigRedButton(buttonAction);
});
injector.constant('buttonAction', launchRocket);
function createInjector() {
  
  var cache = {};
 
  function constant(name, value) {
    cache[name] = value;
  }
 
  function factory(name, factoryFn) {
    // todo
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      return cache[argName];
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; //*
  var providerCache = {}; //*

  function constant(name, value) {
    instanceCache[name] = value; //*
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn; //*
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {







      return instanceCache[name]; //*
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      if (instanceCache.hasOwnProperty(argName)) {
        return instanceCache[argName];
      } else if (providerCache.hasOwnProperty(argName)) {
        var provider = providerCache[argName];



      }
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      if (instanceCache.hasOwnProperty(argName)) {
        return instanceCache[argName];
      } else if (providerCache.hasOwnProperty(argName)) {
        var provider = providerCache[argName];
        var instance = invoke(provider);

        return instance;
      }
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      if (instanceCache.hasOwnProperty(argName)) {
        return instanceCache[argName];
      } else if (providerCache.hasOwnProperty(argName)) {
        var provider = providerCache[argName];
        var instance = invoke(provider);
        instanceCache[argName] = instance;
        return instance;
      }
    });
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}

Show Time!

// Framework

function createInjector() {

  var instanceCache = {};
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name]= value;
  }

  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }

  function invoke(fn) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      if (instanceCache.hasOwnProperty(argName)) {
        return instanceCache[argName];
      } else if (providerCache.hasOwnProperty(argName)) {
        var provider = providerCache[argName];
        var instance = invoke(provider);
        instanceCache[argName]= instance;
        return instance;
      }
    });
    return fn.apply(null, args);
  }

  return {
    constant: constant,
    factory: factory,
    invoke: invoke
  };
}

// App

function launchRocket() {
  console.log('Launching rocket at '+new Date());
}

function BigRedButton(buttonAction) {
  this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
  this._open = true;
};
BigRedButton.prototype.closeLid = function() {
  this._open = false;
};
BigRedButton.prototype.push = function() {
  if (this._open) {
    this.action();
  }
};

function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

var injector = createInjector();
injector.factory('button', function(buttonAction) {
  return new BigRedButton(buttonAction);
});
injector.constant('buttonAction', launchRocket);
injector.invoke(spaceProgram);

Cool, but...

all we do in that factory function is call the BigRedButton constructor, passing in the same arguments.

Can we get rid of this unnecessary wrapper?

injector.factory('button', function(buttonAction) {
  return new BigRedButton(buttonAction);
});

Services

A service takes a constructor function instead of a plain old function.

In addition to invoking that function with dependency injection, it also sets up a new object with the function's prototype chain.

injector.service('button', BigRedButton);
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function service(name, Constructor) {
    factory(name, function() {
      // todo


    });
  }

  function invoke(fn) {
    //...
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    service: service, //*
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function service(name, Constructor) {
    factory(name, function() {
      var instance = Object.create(Constructor.prototype);


    });
  }

  function invoke(fn) {
    //...
    return fn.apply(null, args);
  }
 
  return {
    constant: constant,
    factory: factory,
    service: service,
    invoke: invoke
  };
}
function createInjector() {
  var instanceCache = {}; 
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name] = value;
  }
 
  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }
 
  function service(name, Constructor) {
    factory(name, function() {
      var instance = Object.create(Constructor.prototype);
      invoke(Constructor, instance);
      return instance;
    });
  }

  function invoke(fn, self) { // *
    //...
    return fn.apply(self, args); // *
  }
 
  return {
    constant: constant,
    factory: factory,
    service: service,
    invoke: invoke
  };
}

What we need to do

  • Construct a new object with the prototype chain of the given constructor
  • Invoke the given constructor with dependency injection

Show Time!

// Framework

function createInjector() {

  var instanceCache = {};
  var providerCache = {};

  function constant(name, value) {
    instanceCache[name]= value;
  }

  function factory(name, factoryFn) {
    providerCache[name] = factoryFn;
  }

  function service(name, Constructor) {
    factory(name, function() {
      var instance = Object.create(Constructor.prototype);
      invoke(Constructor, instance);
      return instance;
    });
  }

  function invoke(fn, self) {
    var argsString = fn.toString()
      .match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
    var argNames = argsString.split(',').map(function(argName) {
      return argName.replace(/\s*/g, '');
    });
    var args = argNames.map(function(argName) {
      if (instanceCache.hasOwnProperty(argName)) {
        return instanceCache[argName];
      } else if (providerCache.hasOwnProperty(argName)) {
        var provider = providerCache[argName];
        var instance = invoke(provider);
        instanceCache[argName]= instance;
        return instance;
      }
    });
    return fn.apply(self, args);
  }

  return {
    constant: constant,
    factory: factory,
    service: service,
    invoke: invoke
  };
}

// App

function launchRocket() {
  console.log('Launching rocket at '+new Date());
}

function BigRedButton(buttonAction) {
  this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
  this._open = true;
};
BigRedButton.prototype.closeLid = function() {
  this._open = false;
};
BigRedButton.prototype.push = function() {
  if (this._open) {
    this.action();
  }
};

function spaceProgram(button) {
  button.openLid();
  button.push();
  button.closeLid();
}

var injector = createInjector();
injector.service('button', BigRedButton);
injector.constant('buttonAction', launchRocket);
injector.invoke(spaceProgram);

 

Questions?

AngularJS Dependency Injection

By eladdo92

AngularJS Dependency Injection

  • 503