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

Commit 603fe0d

Browse files
juliemrIgorMinar
authored andcommitted
feat(angular.bootstrap): support deferred bootstrap
This features enables tools like Batarang and test runners to hook into angular's bootstrap process and sneak in more modules into the DI registry which can replace or augment DI services for the purpose of instrumentation or mocking out heavy dependencies. If window.name contains prefix NG_DEFER_BOOTSTRAP! when angular.bootstrap is called, the bootstrap process will be paused until angular.resumeBootstrap is called. angular.resumeBootstrap takes an optional array of modules that should be added to the original list of modules that the app was about to be bootstrapped with.
1 parent 485f104 commit 603fe0d

File tree

2 files changed

+106
-18
lines changed

2 files changed

+106
-18
lines changed

src/Angular.js

+33-17
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var /** holds major version number for IE or NaN for real browsers */
5858
toString = Object.prototype.toString,
5959

6060

61-
_angular = window.angular,
61+
_angular = window.angular,
6262
/** @name angular */
6363
angular = window.angular || (window.angular = {}),
6464
angularModule,
@@ -964,22 +964,38 @@ function angularInit(element, bootstrap) {
964964
* @returns {AUTO.$injector} Returns the newly created injector for this app.
965965
*/
966966
function bootstrap(element, modules) {
967-
element = jqLite(element);
968-
modules = modules || [];
969-
modules.unshift(['$provide', function($provide) {
970-
$provide.value('$rootElement', element);
971-
}]);
972-
modules.unshift('ng');
973-
var injector = createInjector(modules);
974-
injector.invoke(
975-
['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){
976-
scope.$apply(function() {
977-
element.data('$injector', injector);
978-
compile(element)(scope);
979-
});
980-
}]
981-
);
982-
return injector;
967+
var resumeBootstrapInternal = function() {
968+
element = jqLite(element);
969+
modules = modules || [];
970+
modules.unshift(['$provide', function($provide) {
971+
$provide.value('$rootElement', element);
972+
}]);
973+
modules.unshift('ng');
974+
var injector = createInjector(modules);
975+
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
976+
function(scope, element, compile, injector) {
977+
scope.$apply(function() {
978+
element.data('$injector', injector);
979+
compile(element)(scope);
980+
});
981+
}]
982+
);
983+
return injector;
984+
};
985+
986+
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
987+
988+
if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
989+
return resumeBootstrapInternal();
990+
}
991+
992+
window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
993+
angular.resumeBootstrap = function(extraModules) {
994+
forEach(extraModules, function(module) {
995+
modules.push(module);
996+
});
997+
resumeBootstrapInternal();
998+
};
983999
}
9841000

9851001
var SNAKE_CASE_REGEXP = /[A-Z]/g;

test/AngularSpec.js

+73-1
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ describe('angular', function() {
658658
var element = jqLite('<div>{{1+2}}</div>');
659659
var injector = angular.bootstrap(element);
660660
expect(injector).toBeDefined();
661-
expect(element.data('$injector')).toBe(injector);
661+
expect(element.injector()).toBe(injector);
662662
dealoc(element);
663663
});
664664

@@ -672,6 +672,78 @@ describe('angular', function() {
672672
expect(element.html()).toBe('{{1+2}}');
673673
dealoc(element);
674674
});
675+
676+
677+
describe('deferred bootstrap', function() {
678+
var originalName = window.name,
679+
element;
680+
681+
beforeEach(function() {
682+
window.name = '';
683+
element = jqLite('<div>{{1+2}}</div>');
684+
});
685+
686+
afterEach(function() {
687+
dealoc(element);
688+
window.name = originalName;
689+
});
690+
691+
692+
it('should wait for extra modules', function() {
693+
window.name = 'NG_DEFER_BOOTSTRAP!';
694+
angular.bootstrap(element);
695+
696+
expect(element.html()).toBe('{{1+2}}');
697+
698+
angular.resumeBootstrap();
699+
700+
expect(element.html()).toBe('3');
701+
expect(window.name).toEqual('');
702+
});
703+
704+
705+
it('should load extra modules', function() {
706+
element = jqLite('<div>{{1+2}}</div>');
707+
window.name = 'NG_DEFER_BOOTSTRAP!';
708+
709+
var bootstrapping = jasmine.createSpy('bootstrapping');
710+
angular.bootstrap(element, [bootstrapping]);
711+
712+
expect(bootstrapping).not.toHaveBeenCalled();
713+
expect(element.injector()).toBeUndefined();
714+
715+
angular.module('addedModule', []).value('foo', 'bar');
716+
angular.resumeBootstrap(['addedModule']);
717+
718+
expect(bootstrapping).toHaveBeenCalledOnce();
719+
expect(element.injector().get('foo')).toEqual('bar');
720+
});
721+
722+
723+
it('should not defer bootstrap without window.name cue', function() {
724+
angular.bootstrap(element, []);
725+
angular.module('addedModule', []).value('foo', 'bar');
726+
727+
expect(function() {
728+
element.injector().get('foo');
729+
}).toThrow('Unknown provider: fooProvider <- foo');
730+
731+
expect(element.injector().get('$http')).toBeDefined();
732+
});
733+
734+
735+
it('should restore the original window.name after bootstrap', function() {
736+
window.name = 'NG_DEFER_BOOTSTRAP!my custom name';
737+
angular.bootstrap(element);
738+
739+
expect(element.html()).toBe('{{1+2}}');
740+
741+
angular.resumeBootstrap();
742+
743+
expect(element.html()).toBe('3');
744+
expect(window.name).toEqual('my custom name');
745+
});
746+
});
675747
});
676748

677749

0 commit comments

Comments
 (0)