Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The magic of Angular Dependency Injection

Alex Rothenberg
April 04, 2013
710

The magic of Angular Dependency Injection

Talk at AngularJS Boston Meetup April 3, 2013

Alex Rothenberg

April 04, 2013
Tweet

Transcript

  1. AngularJS Boston Meetup April 3, 2013 Alex Rothenberg http://www.alexrothenberg.com @alexrothenberg

    https://github.com/alexrothenberg McKinsey & Co. The Magic of Angular Dependency Injection Thursday, April 4, 13
  2. var MyController = function() { var $scope = new Scope()

    var $location = LocationFactory.gimme() // load the rest... // now do some work } Thursday, April 4, 13
  3. This is the Angular Way var MyController = function($scope, $filter,

    $location, $timeout, $http, $routeParams){ // start using these services } Thursday, April 4, 13
  4. 1. Don’t hardcode your dependencies 2. Let your caller pass

    them in What is Dependency Injection? Thursday, April 4, 13
  5. it("should get the gihub emoji", function() { fakeAjax = mock

    fakeAjax.expectGET('https://api.github.com') .respond({ "emojis_url": "something" }); onClick() fakeDom.should.have.called('html') .with('something') }); Set “something” Expect “something” Thursday, April 4, 13
  6. it("should get the gihub emoji", function() { fakeAjax = mock

    fakeAjax.expectGET('https://api.github.com') .respond({ "emojis_url": "something" }); onClick(fakeDom, fakeAjax) fakeDom.should.have.called('html') .with('something') }); Use fake services Thursday, April 4, 13
  7. var onClick = function(ajaxer, domer) { ajaxer.get("https://api.github.com", function(data) { domer('#res').html(data.emojis_url);

    } ); } $('#my-button').click(onClick); How is jQuery passed in? Thursday, April 4, 13
  8. <html ng-app> <head> <script src="angular.js"></script> <script> var MyController = function($scope)

    { $scope.name = 'World' } </script> </head> <body ng-controller="MyController"> Hello {{ name }} </body> </html> Thursday, April 4, 13
  9. <html ng-app> <head> <script src="angular.js"></script> <script> var MyController = function($scope)

    { $scope.name = 'World' } </script> </head> <body ng-controller="MyController"> Hello {{ name }} </body> </html> Thursday, April 4, 13
  10. <html ng-app> <head> <script src="angular.js"></script> <script> var MyController = function($scope)

    { $scope.name = 'World' } </script> </head> <body ng-controller="MyController"> Hello {{ name }} </body> </html> Thursday, April 4, 13
  11. <html ng-app> <head> <script src="angular.js"></script> <script> var MyController = function($scope)

    { $scope.name = 'World' } </script> </head> <body ng-controller="MyController"> Hello {{ name }} </body> </html> Thursday, April 4, 13
  12. var scope, ctrl; beforeEach(inject(function($rootScope, $controller) { scope = $rootScope.$new(); ctrl

    = $controller(MyController, {$scope: scope}); })); it('should set name to "World"', function() { expect(scope.name).toEqual('World'); }); Thursday, April 4, 13
  13. var scope, ctrl; beforeEach(inject(function($rootScope, $controller) { scope = $rootScope.$new(); ctrl

    = $controller(MyController, {$scope: scope}); })); it('should set name to "World"', function() { expect(scope.name).toEqual('World'); }); Thursday, April 4, 13
  14. var scope, ctrl; beforeEach(inject(function($rootScope, $controller) { scope = $rootScope.$new(); ctrl

    = $controller(MyController, {$scope: scope}); })); it('should set name to "World"', function() { expect(scope.name).toEqual('World'); }); Thursday, April 4, 13
  15. var scope, ctrl; beforeEach(inject(function($rootScope, $controller) { scope = $rootScope.$new(); ctrl

    = $controller(MyController, {$scope: scope}); })); it('should set name to "World"', function() { expect(scope.name).toEqual('World'); }); Thursday, April 4, 13
  16. Injectable The dependency injection in AngularJS allows you to declaratively

    describe how your application is wired. This means that your application needs no main() method which is usually an unmaintainable mess. Dependency injection is also a core to AngularJS. This means that any component which does not fit your needs can easily be replaced. Testable AngularJS was designed from ground up to be testable. It encourages behavior-view separation, comes pre-bundled with mocks, and takes full advantage of dependency injection. It also comes with end-to-end scenario runner which eliminates test flakiness by understanding the inner workings of AngularJS. http://angularjs.org/ Thursday, April 4, 13
  17. Injectable The dependency injection in AngularJS allows you to declaratively

    describe how your application is wired. This means that your application needs no main() method which is usually an unmaintainable mess. Dependency injection is also a core to AngularJS. This means that any component which does not fit your needs can easily be replaced. Testable AngularJS was designed from ground up to be testable. It encourages behavior-view separation, comes pre-bundled with mocks, and takes full advantage of dependency injection. It also comes with end-to-end scenario runner which eliminates test flakiness by understanding the inner workings of AngularJS. http://angularjs.org/ Dependency injection is also core to AngularJS. Thursday, April 4, 13
  18. Injectable The dependency injection in AngularJS allows you to declaratively

    describe how your application is wired. This means that your application needs no main() method which is usually an unmaintainable mess. Dependency injection is also a core to AngularJS. This means that any component which does not fit your needs can easily be replaced. Testable AngularJS was designed from ground up to be testable. It encourages behavior-view separation, comes pre-bundled with mocks, and takes full advantage of dependency injection. It also comes with end-to-end scenario runner which eliminates test flakiness by understanding the inner workings of AngularJS. http://angularjs.org/ AngularJS was designed from ground up to be testable Thursday, April 4, 13
  19. 1. What is Dependency Injection 2. Why Dependency Injection is

    useful 3. How we use Dependency Injection in Angular 4. How Angular implements DI 5. Use our knowledge to break Angular 6. Thursday, April 4, 13
  20. 1. What is Dependency Injection 2. Why Dependency Injection is

    useful 3. How we use Dependency Injection in Angular 4. How Angular implements DI 5. Use our knowledge to break Angular 6. We’ll unbreak it Thursday, April 4, 13
  21. <body ng-controller="MyController"> <h1>Recent commits to AngularJS</h1> <ul> <li ng-repeat="commit in

    commits"> {{ commit.commit.committer.date | date}} {{ commit.sha }} {{commit.commit.message}} </li> </ul> </body> </html> Thursday, April 4, 13
  22. <body ng-controller="MyController"> <h1>Recent commits to AngularJS</h1> <ul> <li ng-repeat="commit in

    commits"> {{ commit.commit.committer.date | date}} {{ commit.sha }} {{commit.commit.message}} </li> </ul> </body> </html> Thursday, April 4, 13
  23. <body ng-controller="MyController"> <h1>Recent commits to AngularJS</h1> <ul> <li ng-repeat="commit in

    commits"> {{ commit.commit.committer.date | date}} {{ commit.sha }} {{commit.commit.message}} </li> </ul> </body> </html> Thursday, April 4, 13
  24. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  25. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  26. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  27. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  28. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  29. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  30. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  31. var $httpBackend, scope, ctrl; beforeEach(inject(function($rootScope, _$httpBackend_, $controller) { $httpBackend =

    _$httpBackend_; $httpBackend.expectGET('https://api.github.com/repos/angular/ angular.js/commits') .respond([ { "sha": "3a81dd" }, { "sha": "df9bff" } ]); scope = $rootScope.$new(); ctrl = $controller(MyController, {$scope: scope}); })); it('should get 2 commits from mock ajax', function() { $httpBackend.flush(); expect(scope.commits).toEqualData( [{ "sha": "3a81dd" }, { "sha": "df9bff" }]); }); Thursday, April 4, 13
  32. How Angular loads your controller 1. Figure out what MyController

    depends on $scope $http Thursday, April 4, 13
  33. How Angular loads your controller 2. Finds a service that

    implements each one $scope => // the scope {} $http => // the http service Thursday, April 4, 13
  34. How Angular loads your controller 3. Calls MyController with injected

    dependencies MyController(the-scope, the-http-svc); Thursday, April 4, 13
  35. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG

    = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, fnText, argDecl, last; $inject = []; fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); return $inject; } Angular’s annotate() Thursday, April 4, 13
  36. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG

    = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, fnText, argDecl, last; $inject = []; fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); return $inject; } Thursday, April 4, 13
  37. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG

    = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, fnText, argDecl, last; $inject = []; fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); return $inject; } Thursday, April 4, 13
  38. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG

    = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, fnText, argDecl, last; $inject = []; fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); return $inject; } Thursday, April 4, 13
  39. How Angular loads your controller 1 Uses “annotate” what your

    parameters are called [”$scope”, “$http”] or [“$http”, ”$scope”] Thursday, April 4, 13
  40. How Angular loads your controller 1 Uses “annotate” what your

    parameters are called 2. Finds a service that implements each one 3. Calls MyController with injected dependencies Thursday, April 4, 13
  41. function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof

    fn == 'function') { // all the regular expression code } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn') $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } Thursday, April 4, 13
  42. function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof

    fn == 'function') { // all the regular expression code } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn') $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } Thursday, April 4, 13
  43. function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof

    fn == 'function') { // all the regular expression code } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn') $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } Thursday, April 4, 13
  44. function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof

    fn == 'function') { // all the regular expression code } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn') $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } Thursday, April 4, 13
  45. function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof

    fn == 'function') { // all the regular expression code } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn') $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } Thursday, April 4, 13