-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat($injector): ability to load new modules after bootstrapping #16224
feat($injector): ability to load new modules after bootstrapping #16224
Conversation
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
1 similar comment
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
The original commit came from #4694 and the author did sign CLA. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments. LGTM overall.
src/auto/injector.js
Outdated
* | ||
* @description | ||
* | ||
* **This is an dangerous API, which you use at your own risk!** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an --> a
src/auto/injector.js
Outdated
* }); | ||
* ``` | ||
* | ||
* @param {Module[]} mods A collection of modules to load. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, this should be an array of strings. (We also support functions, but this seems to be an undocumented feature.)
We also use the format Array.<...>
for array arguments.
src/auto/injector.js
Outdated
* Add the specified modules to the current injector. | ||
* | ||
* This method will add each of the injectables to the injector and execute all of the run blocks | ||
* for each module passed to the method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might (or might not) be worth also mentioning that:
-
Config blocks are executed as well. (Since we explicitly mention run blocks, it might sounds like config block
as are not executed.) -
Loading already loaded modules has no effect (and their config/run blocks aren't executed either).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blockas -> blocks :-P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also test what happens with config blocks eh?
src/auto/injector.js
Outdated
* Here is an example of loading a bundle of modules, with a utility method called `getScript`: | ||
* | ||
* ```javascript | ||
* app.service('AmdLoader', function ($injector, $log) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service --> factory (?)
(service
works but can be confusing for beginners.)
src/auto/injector.js
Outdated
* $log.debug('AMD >> Loading Angular module [' + moduleName + '] ...'); | ||
* | ||
* if (config.modules) { | ||
* $injector.loadNewModules(config.modules); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this? Shouldn't the lazy-loaded module depend on these (as in angular.module('someModule', [...dependent modules here...])
), in which case loadNewModules
would automatically load them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure to be honest, this was a bit of a cut-n-paste job.
src/auto/injector.js
Outdated
* $injector.loadNewModules([moduleName]); | ||
* $log.debug('AMD >> Angular module [' + moduleName + '] loaded.'); | ||
* } | ||
* }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I find the example unnecessarily complicated. I would expect something like:
app.factory('loadModule', function($injector) {
return function loadModule(moduleName, bundleUrl) {
return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); });
};
})
test/auto/injectorSpec.js
Outdated
angular.module('b', ['a'], function() { log += 'b'; }).run(function() { log += 'B'; }); | ||
injector.loadNewModules([ | ||
'b', | ||
valueFn(function() { log += 'C'; }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add some config fns here two 😁
test/auto/injectorSpec.js
Outdated
|
||
it('should be able to register a service from a new module', function() { | ||
var injector = createInjector([]); | ||
angular.module('a'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean angular.module('a', [])
?
@@ -701,6 +747,11 @@ function createInjector(modulesToLoad, strictDi) { | |||
instanceInjector.strictDi = strictDi; | |||
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
I have reworked following the comments. |
8a44f9c
to
b1c8a43
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One real (docs related) comment and a handful of nitpicks 😁
LGTM otherwise.
💯 for the added tests btw
src/auto/injector.js
Outdated
* ```javascript | ||
* app.factory('loadModule', function($injector) { | ||
* return function loadModule(moduleName, bundleUrl) { | ||
* return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something got really wrong with the indentation in the three lines above 😛
* }) | ||
* ``` | ||
* | ||
* @param {Array<String|Function|Array>=} mods an array of modules to load into the application. |
There was a problem hiding this comment.
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
) 😇
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😛
There was a problem hiding this comment.
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
? 😛
src/auto/injector.js
Outdated
* @param {Array<String|Function|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}* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the trailing *
intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no
test/auto/injectorSpec.js
Outdated
expect(log).toEqual(['initial', 'created', 'config1', 'config2']); | ||
10000 }); | ||
|
||
it('should execute run blocks and config blocks in the correct order', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent wording: run/config blocks
(vs run/configBlocks
above) 😱
test/auto/injectorSpec.js
Outdated
|
||
it('should execute run blocks and config blocks in the correct order', function() { | ||
var log = []; | ||
angular.module('initial', [], function() { log.push(1)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing semicolon and space inside the function. 💣
test/auto/injectorSpec.js
Outdated
it('should be able to register a service from a new module', function() { | ||
var injector = createInjector([]); | ||
angular.module('a', []); | ||
angular.module('a').factory('aService', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason to have these two as separate lines (here and below) 😁
test/auto/injectorSpec.js
Outdated
var injector = createInjector([]); | ||
angular.module('a', []); | ||
angular.module('a').factory('aService', function() { | ||
return { sayHello: function() { return 'Hello'; } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be no space after/before {
/}
for objects 🏄
test/auto/injectorSpec.js
Outdated
}); | ||
injector.loadNewModules(['a']); | ||
injector.invoke(function($filter) { | ||
var filter = $filter('aFilter'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Save a line with injector.invoke(function(aFilterFilter) {
(Bonus points for the hidden filterFilter
reference 😛)
test/auto/injectorSpec.js
Outdated
var scope = $rootScope.$new(); | ||
var html = '<div a-directive></div>'; | ||
var elem = angular.element(html); | ||
$compile(elem)(scope); // compile and link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Save up to three(3) lines with:
var elem = $compile('<div a-directive></div>')($rootScope);
$rootScope.$digest();
🌿
test/auto/injectorSpec.js
Outdated
var injector = createInjector(['ng']); | ||
angular.module('a', []); | ||
angular.module('a').directive('aDirective', function() { | ||
return { template: 'test directive' }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Way too much whitespace inside object 😇
b1c8a43
to
747384a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (except for a couple of indentation issues - including #16224 (comment) 😁)
test/auto/injectorSpec.js
Outdated
var injector = createInjector(['initial']); | ||
log.push('created'); | ||
|
||
angular.module('a', [], function() { log.push(4);}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing whitespace after ;
🔢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume one of the common use cases is to load additional modules when the user navigates inside the application, i.e. before a route is loaded.
This means the application must download the module and load it, but only for the first time (as the modules cannot be unloaded).
I don't see a mechanism for checking if a module has been loaded already. We don't expose the list in the injector, either. Should we add a convenience method hasModule()
?
You could always check if a specific service is in the DI, but this won't work for modules that only load components etc.
It's not enough that the injector won't load a module twice either, it's the download that must be avoided. Correct caching could handle that ofc.
Georgios pointed out that we have https://docs.angularjs.org/api/auto/service/$injector#modules for that
src/auto/injector.js
Outdated
* ```javascript | ||
* app.factory('loadModule', function($injector) { | ||
* return function loadModule(moduleName, bundleUrl) { | ||
* return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use fetch
in-place here for getScript
. Only thing that might happen is that people use it and complain that it doesn't work in IE9 ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Narretz I think the difference is that getScript
also executes the script.
* }) | ||
* ``` | ||
* | ||
* @param {Array<String|Function|Array>=} mods an array of modules to load into the application. |
There was a problem hiding this comment.
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
? 😛
Doesn't $injector.modules cover that? |
It does, thanks. I forgot we added that. Maybe we could add a note about the property to the docs. |
@Narretz do you mean add a link to the |
@petebacondarwin that's right |
747384a
to
860f9e9
Compare
The new method `$injector.loadNewModules(modules)` 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. * The application developer is responsible for loading the code containing the modules. * Modules cannot be unloaded. * Previously loaded modules will not be reloaded. * Previously compiled HTML will not be affected by newly loaded directives, filters and components.
860f9e9
to
8186065
Compare
Cherry picked to 1.6.x as 6e78fee |
The new method
$injector.loadNewModules(modules)
will add each of theinjectables to the injector and execute all of the run blocks for each
module passed to the method.
the modules.
filters and components.
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
What is the current behavior? (You can also link to an open issue here)
What is the new behavior (if this is a feature change)?
Does this PR introduce a breaking change?
Please check if the PR fulfills these requirements
Other information: