Dependency Injection @ AngularJS

32
Dependency Injection @ AngularJS Ran Mizrahi (@ranm8) Open Source Dpt. Leader @ CodeOasis Monday, September 9, 13

description

Ran Mizrahi from CodeOasis gave a lecture about Angular.js dependency injections, as part of an advanced Angular meetup of the Javascript Israel meetup group. Ran covered how Dependency Injection works in Angular and its internals, what are providers and the different helper methods angular have for defining injectables components.

Transcript of Dependency Injection @ AngularJS

Page 1: Dependency Injection @ AngularJS

Dependency Injection@ AngularJS

Ran Mizrahi (@ranm8)Open Source Dpt. Leader @ CodeOasis

Monday, September 9, 13

Page 2: Dependency Injection @ AngularJS

About CodeOasis

• CodeOasis specializes in cutting-edge web solutions.

• Large variety of customers (from startups to enterprises).

• We LOVE JavaScript (-:

• Technologies we love:

• Symfony2• AngularJS • nodeJS • HTML5• CSS3

• Our Microsoft department works with C#, WPF, etc.

Monday, September 9, 13

Page 3: Dependency Injection @ AngularJS

What is Dependency Injection???

Monday, September 9, 13

Page 4: Dependency Injection @ AngularJS

The Problemfunction SessionStore() {}

SessionStore.prototype = { set: function(name, value) { window.localStorage.setItem(name, value); }, get: function(name) { return window.localStorage.getItem(name); }}

function User() { this.sessionStore = new SessionStore();}

User.prototype = { setSession: function(session) { this.sessionStore.set('session', session); }, getSession: function() { return this.sessionStore.get('session'); }}

Monday, September 9, 13

Page 5: Dependency Injection @ AngularJS

The ProblemEverything goes well and the code is working.But now we have to change the session name...

We can use a global variable:sessionName = 'newSessionName';

function User() { this.sessionStore = new SessionStore();}

// ...

Maybe we should hard-coded pass it to the store:function User() { this.sessionStore = new SessionStore('newSessionName');}

// ...

Monday, September 9, 13

Page 6: Dependency Injection @ AngularJS

The Problem

How would you change the entire SessionStore implementation?

CookiesServer

...

function User() { this.sessionStore = registry.get('SessionStore');}

// ...

Should we use some global registry object??

How would we test the User??

Monday, September 9, 13

Page 7: Dependency Injection @ AngularJS

The Problem

All those solutions are bad because:

• Couples code to one implementation.

• Hard to configure

• Cannot change the implementation without changing the User prototype.

• Untestable code unless you monkey patch it.

Monday, September 9, 13

Page 8: Dependency Injection @ AngularJS

Dependency Injection

Dependency injection is a software design pattern that allows the

removal of hard-coded dependencies and makes it possible

to change them.-- Wikipedia

Monday, September 9, 13

Page 9: Dependency Injection @ AngularJS

Dependency Injection To The Rescue!

Instead of instantiating the SessionStore within the User...

function User(sessionStore) { this.sessionStore = sessionStore;}

// ...

var user = new User(new SessionStore());

JUST INJECT IT!!!

Monday, September 9, 13

Page 10: Dependency Injection @ AngularJS

Advantages Of Using DI

Use different session store strategies...

function User(sessionStore) { this.sessionStore = sessionStore;}

// ...

var user = new User(new CookieSessionStore());

Monday, September 9, 13

Page 11: Dependency Injection @ AngularJS

Advantages Of Using DI

Configuration becomes easy...

function User(sessionStore) { this.sessionStore = sessionStore;}

// ...

var user = new User(new SessionStore('mySessionName'));

Monday, September 9, 13

Page 12: Dependency Injection @ AngularJS

Advantages Of Using DI

Now we can easily mock the SessionStore (for testing purposes)...

function User(sessionStore) { this.sessionStore = sessionStore;}

// ...

var user = new User(new MockSessionStore());

Monday, September 9, 13

Page 13: Dependency Injection @ AngularJS

All those things makes our code

MAINTAINABLEbecause we’ve..

Less codeExtensibleTestable

Monday, September 9, 13

Page 14: Dependency Injection @ AngularJS

But How To Scale It?!!?!?

But what if we have a slightly more complex application..

And we do that in many different places around our application:

var user = new User(new SessionStore(new SomeThirdParty(jQuery), new Http(new Thing())), new Something());

//....

var user = new User(new SessionStore(new SomeThirdParty(jQuery), new Http(new Thing())), new Something());

//....

var user = new User(new SessionStore(new SomeThirdParty(jQuery), new Http(new Thing())), new Something());

Monday, September 9, 13

Page 15: Dependency Injection @ AngularJS

Don’t worry!

Monday, September 9, 13

Page 16: Dependency Injection @ AngularJS

DI Container To The Rescue

Monday, September 9, 13

Page 17: Dependency Injection @ AngularJS

DI Container (e.g. Injector, Provider)

• Instantiates objects and their dependencies on demand.

• Allows to configure objects before instantiation.

• Can instantiate new objects on demand or provide existing ones from cache.

• The objects must never know they are being managed by the container.

• A container should be able to manage any object.

Monday, September 9, 13

Page 18: Dependency Injection @ AngularJS

AngularJS DI

Monday, September 9, 13

Page 19: Dependency Injection @ AngularJS

How The DI Injector Works?!

• Angular inject the requested service by the function argument names.

• Can also be done with an array.

• Once requested Angular’s injector would instantiate the requested service and inject it.

angular.module('myModule') .controller('MyCtrl', MyCtrl); function MyCtrl($http) { $http.get('http://google.com').then(getTheMonkey); }

Monday, September 9, 13

Page 20: Dependency Injection @ AngularJS

The Array Notation

• Allows minifiers to preserve argument names for the dependency injection to work with.

• More flexible - Separates dependency declaration from your unit.

angular.module('myModule') .controller('MyCtrl', ['$http', MyCtrl]); function MyCtrl($http) { $http.get('http://google.com').then(getTheMonkey); }

Monday, September 9, 13

Page 21: Dependency Injection @ AngularJS

Changing The Implementationangular.module('myModule') .controller('MyCtrl', ['myHttp', MyCtrl]) .factory('myHttp', ['$q', myHttp]); function myHttp($q) { return { get: function() { var defer = $q.defer(); // Do something with XHR and return a promise... return defer.promise; } };}

function MyCtrl($http) { $http.get('http://google.com').then(someCallback); }

• Changes the implementation by changing only the array notation.

• Angular’s injector instantiates the dependencies of each dependency.

Monday, September 9, 13

Page 22: Dependency Injection @ AngularJS

How Do I Use It?

Monday, September 9, 13

Page 23: Dependency Injection @ AngularJS

Service/Factory

A service is the Angular way of exposing objects within the $injector.

• Can have multiple dependencies that will be injected when invoked.

angular.module('myModule') .service('myHttp', ['$q', myHttp]); function myHttp($q) { this.get = function() { var defer = $q.defer(); // Do something with XHR and return a promise... return defer.promise; };}

Monday, September 9, 13

Page 24: Dependency Injection @ AngularJS

Factory

Shorthand for registering services without a constructor function (assigned to $get property directly).

angular.module('myModule') .factory('myHttp', ['$q', myHttp]);

function myHttp($q) { return { get: function() { var defer = $q.defer(); // Do something with XHR and return a promise... return defer.promise; } };}

Monday, September 9, 13

Page 25: Dependency Injection @ AngularJS

Provider

Registers a provider to a service.

• Allows the save configuration state to the service.

• Only constants can be injected.

• The provider is a constructor function.

• $get is a function that returns the actual service.

Monday, September 9, 13

Page 26: Dependency Injection @ AngularJS

Provider

angular.module('myModule') .provider('myHttp', myHttp); function myHttp() { var baseUrl; this.baseUrl = function(value) { if (!value) { return baseUrl; } baseUrl = value; }; this.$get = ['$q', function() { // myHttp service implementation... }];}

Monday, September 9, 13

Page 27: Dependency Injection @ AngularJS

Configuration Phase vs. Run Phase Configuration Phase

Run Phase

• Runs before any service was instantiated.

• Only providers can be injected.

• Each provider is injected with the “Provider” suffix (e.g. $locationProvider)

• Allows to purely configure the services state.

• Services state should be not be changed now (already configured during run phase).

• Providers now cannot be injected.

Monday, September 9, 13

Page 28: Dependency Injection @ AngularJS

Config

Use the service providers to configure the services state during the config phase to the run phase.

angular.module('myModule') .config(['myHttpProvider', '$locationProvider', appConfig]);

function appConfig(myHttpProvider, $locationProvider) { // Configure app to use HTML5 History API.. $locationProvider.html5Mode(true); // Configure my service baseUrl.. myHttpProvider.baseUrl('http://www.example.com');}

Monday, September 9, 13

Page 29: Dependency Injection @ AngularJS

Value

Value is a shorthand to register a simple value as a service.

angular.module('myModule') .value('myHttp', 'some string');

Monday, September 9, 13

Page 30: Dependency Injection @ AngularJS

Constant

Constant is the same as value, but unlike value it can be injected to configuration function

angular.module('myModule') .constant('someConstant', '123');

Monday, September 9, 13

Page 31: Dependency Injection @ AngularJS

Angular’s DI In Testingdescribe('myHttp', function() {

var mockQ = { then: function(){}

}, http; beforeEach(module(function($provide) { $provide.value('$q', mockQ); })); beforeEach(inject(function(myHttp) { http = myHttp; })); describe('#get()', function() { it('should return a promise', function() { // test your code here }); });});

Monday, September 9, 13

Page 32: Dependency Injection @ AngularJS

Questions?Thank you!

Monday, September 9, 13