diff --git a/src/auto/injector.js b/src/auto/injector.js index d392653da4d7..0592c35d333f 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -337,7 +337,46 @@ function annotate(fn, strictDi, name) { * * @returns {Array.} The names of the services which the function requires. */ - +/** + * @ngdoc method + * @name $injector#loadNewModules + * + * @description + * + * **This is a dangerous API, which you use at your own risk!** + * + * Add the specified modules to the current injector. + * + * This method will add each of the injectables to the injector and execute all of the config and run + * blocks for each module passed to the method. + * + * If a module has already been loaded into the injector then it will not be loaded again. + * + * * The application developer is responsible for loading the code containing the modules; and for + * ensuring that lazy scripts are not downloaded and executed more often that desired. + * * Previously compiled HTML will not be affected by newly loaded directives, filters and components. + * * Modules cannot be unloaded. + * + * You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded + * into the injector, which may indicate whether the script has been executed already. + * + * ## Example + * + * Here is an example of loading a bundle of modules, with a utility method called `getScript`: + * + * ```javascript + * app.factory('loadModule', function($injector) { + * return function loadModule(moduleName, bundleUrl) { + * return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); }); + * }; + * }) + * ``` + * + * @param {Array=} mods an array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a `config` block. + * See: {@link angular.module modules} + */ /** @@ -701,6 +740,11 @@ function createInjector(modulesToLoad, strictDi) { instanceInjector.strictDi = strictDi; forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); + instanceInjector.loadNewModules = function(mods) { + forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); }); + }; + + return instanceInjector; //////////////////////////////////// diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 2e272b356a1e..7f5254e201e6 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -19,7 +19,7 @@ describe('injector.modules', function() { .info({ version: '1.2' }) .provider('test', ['$injector', function($injector) { providerInjector = $injector; - return { $get: function() {} }; + return {$get: function() {}}; }]); module('test1'); // needed to ensure that the provider blocks are executed @@ -152,6 +152,164 @@ describe('injector', function() { expect($injector).not.toBe(providerInjector); })); + + describe('loadNewModules', function() { + it('should be defined on $injector', function() { + var injector = createInjector([]); + expect(injector.loadNewModules).toEqual(jasmine.any(Function)); + }); + + it('should allow new modules to be added after injector creation', function() { + angular.module('initial', []); + var injector = createInjector(['initial']); + expect(injector.modules['initial']).toBeDefined(); + expect(injector.modules['lazy']).toBeUndefined(); + angular.module('lazy', []); + injector.loadNewModules(['lazy']); + expect(injector.modules['lazy']).toBeDefined(); + }); + + it('should execute runBlocks of new modules', function() { + var log = []; + angular.module('initial', []).run(function() { log.push('initial'); }); + var injector = createInjector(['initial']); + log.push('created'); + + angular.module('a', []).run(function() { log.push('a'); }); + injector.loadNewModules(['a']); + expect(log).toEqual(['initial', 'created', 'a']); + }); + + it('should execute configBlocks of new modules', function() { + var log = []; + angular.module('initial', []).config(function() { log.push('initial'); }); + var injector = createInjector(['initial']); + log.push('created'); + + angular.module('a', [], function() { log.push('config1'); }).config(function() { log.push('config2'); }); + injector.loadNewModules(['a']); + expect(log).toEqual(['initial', 'created', 'config1', 'config2']); + }); + + it('should execute runBlocks and configBlocks in the correct order', function() { + var log = []; + angular.module('initial', [], function() { log.push(1); }) + .config(function() { log.push(2); }) + .run(function() { log.push(3); }); + var injector = createInjector(['initial']); + log.push('created'); + + angular.module('a', [], function() { log.push(4); }) + .config(function() { log.push(5); }) + .run(function() { log.push(6); }); + injector.loadNewModules(['a']); + expect(log).toEqual([1, 2, 3, 'created', 4, 5, 6]); + }); + + it('should load dependent modules', function() { + angular.module('initial', []); + var injector = createInjector(['initial']); + expect(injector.modules['initial']).toBeDefined(); + expect(injector.modules['lazy1']).toBeUndefined(); + expect(injector.modules['lazy2']).toBeUndefined(); + angular.module('lazy1', ['lazy2']); + angular.module('lazy2', []); + injector.loadNewModules(['lazy1']); + expect(injector.modules['lazy1']).toBeDefined(); + expect(injector.modules['lazy2']).toBeDefined(); + }); + + it('should execute blocks of new modules in the correct order', function() { + var log = []; + angular.module('initial', []); + var injector = createInjector(['initial']); + + angular.module('lazy1', ['lazy2'], function() { log.push('lazy1-1'); }) + .config(function() { log.push('lazy1-2'); }) + .run(function() { log.push('lazy1-3'); }); + angular.module('lazy2', [], function() { log.push('lazy2-1'); }) + .config(function() { log.push('lazy2-2'); }) + .run(function() { log.push('lazy2-3'); }); + + injector.loadNewModules(['lazy1']); + expect(log).toEqual(['lazy2-1', 'lazy2-2', 'lazy1-1', 'lazy1-2', 'lazy2-3', 'lazy1-3']); + }); + + it('should not reload a module that is already loaded', function() { + var log = []; + angular.module('initial', []).run(function() { log.push('initial'); }); + var injector = createInjector(['initial']); + expect(log).toEqual(['initial']); + + injector.loadNewModules(['initial']); + expect(log).toEqual(['initial']); + + angular.module('a', []).run(function() { log.push('a'); }); + injector.loadNewModules(['a']); + expect(log).toEqual(['initial', 'a']); + injector.loadNewModules(['a']); + expect(log).toEqual(['initial', 'a']); + + angular.module('b', ['a']).run(function() { log.push('b'); }); + angular.module('c', []).run(function() { log.push('c'); }); + angular.module('d', ['b', 'c']).run(function() { log.push('d'); }); + injector.loadNewModules(['d']); + expect(log).toEqual(['initial', 'a', 'b', 'c', 'd']); + }); + + it('should be able to register a service from a new module', function() { + var injector = createInjector([]); + angular.module('a', []).factory('aService', function() { + return {sayHello: function() { return 'Hello'; }}; + }); + injector.loadNewModules(['a']); + injector.invoke(function(aService) { + expect(aService.sayHello()).toEqual('Hello'); + }); + }); + + + it('should be able to register a controller from a new module', function() { + var injector = createInjector(['ng']); + angular.module('a', []).controller('aController', function($scope) { + $scope.test = 'b'; + }); + injector.loadNewModules(['a']); + injector.invoke(function($controller) { + var scope = {}; + $controller('aController', {$scope: scope}); + expect(scope.test).toEqual('b'); + }); + }); + + + it('should be able to register a filter from a new module', function() { + var injector = createInjector(['ng']); + angular.module('a', []).filter('aFilter', function() { + return function(input) { return input + ' filtered'; }; + }); + injector.loadNewModules(['a']); + injector.invoke(function(aFilterFilter) { + expect(aFilterFilter('test')).toEqual('test filtered'); + }); + }); + + + it('should be able to register a directive from a new module', function() { + var injector = createInjector(['ng']); + angular.module('a', []).directive('aDirective', function() { + return {template: 'test directive'}; + }); + injector.loadNewModules(['a']); + injector.invoke(function($compile, $rootScope) { + var elem = $compile('
')($rootScope); // compile and link + $rootScope.$digest(); + expect(elem.text()).toEqual('test directive'); + elem.remove(); + }); + }); + }); + it('should have a false strictDi property', inject(function($injector) { expect($injector.strictDi).toBe(false); }));