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

Commit 6e78fee

Browse files
Craig Leyshanpetebacondarwin
Craig Leyshan
authored andcommitted
feat($injector): ability to load new modules after bootstrapping
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.
1 parent 6a99eaf commit 6e78fee

File tree

2 files changed

+204
-2
lines changed

2 files changed

+204
-2
lines changed

src/auto/injector.js

+45-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,46 @@ function annotate(fn, strictDi, name) {
337337
*
338338
* @returns {Array.<string>} The names of the services which the function requires.
339339
*/
340-
340+
/**
341+
* @ngdoc method
342+
* @name $injector#loadNewModules
343+
*
344+
* @description
345+
*
346+
* **This is a dangerous API, which you use at your own risk!**
347+
*
348+
* Add the specified modules to the current injector.
349+
*
350+
* This method will add each of the injectables to the injector and execute all of the config and run
351+
* blocks for each module passed to the method.
352+
*
353+
* If a module has already been loaded into the injector then it will not be loaded again.
354+
*
355+
* * The application developer is responsible for loading the code containing the modules; and for
356+
* ensuring that lazy scripts are not downloaded and executed more often that desired.
357+
* * Previously compiled HTML will not be affected by newly loaded directives, filters and components.
358+
* * Modules cannot be unloaded.
359+
*
360+
* You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded
361+
* into the injector, which may indicate whether the script has been executed already.
362+
*
363+
* ## Example
364+
*
365+
* Here is an example of loading a bundle of modules, with a utility method called `getScript`:
366+
*
367+
* ```javascript
368+
* app.factory('loadModule', function($injector) {
369+
* return function loadModule(moduleName, bundleUrl) {
370+
* return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); });
371+
* };
372+
* })
373+
* ```
374+
*
375+
* @param {Array<String|Function|Array>=} mods an array of modules to load into the application.
376+
* Each item in the array should be the name of a predefined module or a (DI annotated)
377+
* function that will be invoked by the injector as a `config` block.
378+
* See: {@link angular.module modules}
379+
*/
341380

342381

343382
/**
@@ -701,6 +740,11 @@ function createInjector(modulesToLoad, strictDi) {
701740
instanceInjector.strictDi = strictDi;
702741
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
703742

743+
instanceInjector.loadNewModules = function(mods) {
744+
forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); });
745+
};
746+
747+
704748
return instanceInjector;
705749

706750
////////////////////////////////////

test/auto/injectorSpec.js

+159-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('injector.modules', function() {
1919
.info({ version: '1.2' })
2020
.provider('test', ['$injector', function($injector) {
2121
providerInjector = $injector;
22-
return { $get: function() {} };
22+
return {$get: function() {}};
2323
}]);
2424
module('test1');
2525
// needed to ensure that the provider blocks are executed
@@ -152,6 +152,164 @@ describe('injector', function() {
152152
expect($injector).not.toBe(providerInjector);
153153
}));
154154

155+
156+
describe('loadNewModules', function() {
157+
it('should be defined on $injector', function() {
158+
var injector = createInjector([]);
159+
expect(injector.loadNewModules).toEqual(jasmine.any(Function));
160+
});
161+
162+
it('should allow new modules to be added after injector creation', function() {
163+
angular.module('initial', []);
164+
var injector = createInjector(['initial']);
165+
expect(injector.modules['initial']).toBeDefined();
166+
expect(injector.modules['lazy']).toBeUndefined();
167+
angular.module('lazy', []);
168+
injector.loadNewModules(['lazy']);
169+
expect(injector.modules['lazy']).toBeDefined();
170+
});
171+
172+
it('should execute runBlocks of new modules', function() {
173+
var log = [];
174+
angular.module('initial', []).run(function() { log.push('initial'); });
175+
var injector = createInjector(['initial']);
176+
log.push('created');
177+
178+
angular.module('a', []).run(function() { log.push('a'); });
179+
injector.loadNewModules(['a']);
180+
expect(log).toEqual(['initial', 'created', 'a']);
181+
});
182+
183+
it('should execute configBlocks of new modules', function() {
184+
var log = [];
185+
angular.module('initial', []).config(function() { log.push('initial'); });
186+
var injector = createInjector(['initial']);
187+
log.push('created');
188+
189+
angular.module('a', [], function() { log.push('config1'); }).config(function() { log.push('config2'); });
190+
injector.loadNewModules(['a']);
191+
expect(log).toEqual(['initial', 'created', 'config1', 'config2']);
192+
});
193+
194+
it('should execute runBlocks and configBlocks in the correct order', function() {
195+
var log = [];
196+
angular.module('initial', [], function() { log.push(1); })
197+
.config(function() { log.push(2); })
198+
.run(function() { log.push(3); });
199+
var injector = createInjector(['initial']);
200+
log.push('created');
201+
202+
angular.module('a', [], function() { log.push(4); })
203+
.config(function() { log.push(5); })
204+
.run(function() { log.push(6); });
205+
injector.loadNewModules(['a']);
206+
expect(log).toEqual([1, 2, 3, 'created', 4, 5, 6]);
207+
});
208+
209+
it('should load dependent modules', function() {
210+
angular.module('initial', []);
211+
var injector = createInjector(['initial']);
212+
expect(injector.modules['initial']).toBeDefined();
213+
expect(injector.modules['lazy1']).toBeUndefined();
214+
expect(injector.modules['lazy2']).toBeUndefined();
215+
angular.module('lazy1', ['lazy2']);
216+
angular.module('lazy2', []);
217+
injector.loadNewModules(['lazy1']);
218+
expect(injector.modules['lazy1']).toBeDefined();
219+
expect(injector.modules['lazy2']).toBeDefined();
220+
});
221+
222+
it('should execute blocks of new modules in the correct order', function() {
223+
var log = [];
224+
angular.module('initial', []);
225+
var injector = createInjector(['initial']);
226+
227+
angular.module('lazy1', ['lazy2'], function() { log.push('lazy1-1'); })
228+
.config(function() { log.push('lazy1-2'); })
229+
.run(function() { log.push('lazy1-3'); });
230+
angular.module('lazy2', [], function() { log.push('lazy2-1'); })
231+
.config(function() { log.push('lazy2-2'); })
232+
.run(function() { log.push('lazy2-3'); });
233+
234+
injector.loadNewModules(['lazy1']);
235+
expect(log).toEqual(['lazy2-1', 'lazy2-2', 'lazy1-1', 'lazy1-2', 'lazy2-3', 'lazy1-3']);
236+
});
237+
238+
it('should not reload a module that is already loaded', function() {
239+
var log = [];
240+
angular.module('initial', []).run(function() { log.push('initial'); });
241+
var injector = createInjector(['initial']);
242+
expect(log).toEqual(['initial']);
243+
244+
injector.loadNewModules(['initial']);
245+
expect(log).toEqual(['initial']);
246+
247+
angular.module('a', []).run(function() { log.push('a'); });
248+
injector.loadNewModules(['a']);
249+
expect(log).toEqual(['initial', 'a']);
250+
injector.loadNewModules(['a']);
251+
expect(log).toEqual(['initial', 'a']);
252+
253+
angular.module('b', ['a']).run(function() { log.push('b'); });
254+
angular.module('c', []).run(function() { log.push('c'); });
255+
angular.module('d', ['b', 'c']).run(function() { log.push('d'); });
256+
injector.loadNewModules(['d']);
257+
expect(log).toEqual(['initial', 'a', 'b', 'c', 'd']);
258+
});
259+
260+
it('should be able to register a service from a new module', function() {
261+
var injector = createInjector([]);
262+
angular.module('a', []).factory('aService', function() {
263+
return {sayHello: function() { return 'Hello'; }};
264+
});
265+
injector.loadNewModules(['a']);
266+
injector.invoke(function(aService) {
267+
expect(aService.sayHello()).toEqual('Hello');
268+
});
269+
});
270+
271+
272+
it('should be able to register a controller from a new module', function() {
273+
var injector = createInjector(['ng']);
274+
angular.module('a', []).controller('aController', function($scope) {
275+
$scope.test = 'b';
276+
});
277+
injector.loadNewModules(['a']);
278+
injector.invoke(function($controller) {
279+
var scope = {};
280+
$controller('aController', {$scope: scope});
281+
expect(scope.test).toEqual('b');
282+
});
283+
});
284+
285+
286+
it('should be able to register a filter from a new module', function() {
287+
var injector = createInjector(['ng']);
288+
angular.module('a', []).filter('aFilter', function() {
289+
return function(input) { return input + ' filtered'; };
290+
});
291+
injector.loadNewModules(['a']);
292+
injector.invoke(function(aFilterFilter) {
293+
expect(aFilterFilter('test')).toEqual('test filtered');
294+
});
295+
});
296+
297+
298+
it('should be able to register a directive from a new module', function() {
299+
var injector = createInjector(['ng']);
300+
angular.module('a', []).directive('aDirective', function() {
301+
return {template: 'test directive'};
302+
});
303+
injector.loadNewModules(['a']);
304+
injector.invoke(function($compile, $rootScope) {
305+
var elem = $compile('<div a-directive></div>')($rootScope); // compile and link
306+
$rootScope.$digest();
307+
expect(elem.text()).toEqual('test directive');
308+
elem.remove();
309+
});
310+
});
311+
});
312+
155313
it('should have a false strictDi property', inject(function($injector) {
156314
expect($injector.strictDi).toBe(false);
157315
}));

0 commit comments

Comments
 (0)