8000 feat($injector): ability to load new modules after bootstrapping by petebacondarwin · Pull Request #16224 · angular/angular.js · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat($injector): ability to load new modules after bootstrapping #16224

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/auto/injector.js 8000
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,46 @@ function annotate(fn, strictDi, name) {
*
* @returns {Array.<string>} 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<String|Function|Array>=} mods an array of modules to load into the application.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the ability to pass functions/arrays as modules (e.g. in angular.module()) is undocumented. I think it makes sense to be consistent between angular.module() and loadNewModules().
(Either documenting or undocumenting in both places is fine by me.)

Also, nit: In most places we use Array.<...> (notice the dot after Array) 😇

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cut and pasted this from the angular.bootstrap() API code: https://github.com/angular/angular.js/blob/master/src/Angular.js#L1773

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😞 - I am not going to block the PR for that then 😛

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why does it say mods instead of modules? 😛

* 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}
*/


/**
Expand Down Expand Up @@ -701,6 +740,11 @@ function createInjector(modulesToLoad, strictDi) {
instanceInjector.strictDi = strictDi;
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth putting this in a method to avoid duplicating it a few lines down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the original PR the loadNewModules was reused at application bootstrap, but this no longer works since we must modify the instanceInjector variable between loading the modules and running the run blocks.

  instanceInjector = protoInstanceInjector.get('$injector');

Are you suggesting that we just make a 'executeRunBlocks` method:

function executeRunBlocks(blocks) {
  forEach(blocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
}

which we then reuse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem to save much and there is very little logic involved so I am inclined not to bother.


instanceInjector.loadNewModules = function(mods) {
forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); });
};


return instanceInjector;

////////////////////////////////////
Expand Down
160 changes: 159 additions & 1 deletion test/auto/injectorSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'); })
.confi 6D4E g(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('<div a-directive></div>')($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);
}));
Expand Down
0