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.

Conflicts:

	src/Angular.js
This commit is contained in:
Julie 2013-02-21 11:55:16 -08:00 committed by Igor Minar
parent 4c428121b9
commit 13968343d4
2 changed files with 105 additions and 17 deletions

View file

@ -944,22 +944,38 @@ function angularInit(element, bootstrap) {
* @returns {AUTO.$injector} Returns the newly created injector for this app.
*/
function bootstrap(element, modules) {
element = jqLite(element);
modules = modules || [];
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
modules.unshift('ng');
var injector = createInjector(modules);
injector.invoke(
['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;
var resumeBootstrapInternal = function() {
element = jqLite(element);
modules = modules || [];
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
modules.unshift('ng');
var injector = createInjector(modules);
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function(scope, element, compile, injector) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;
};
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return resumeBootstrapInternal();
}
window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
angular.resumeBootstrap = function(extraModules) {
forEach(extraModules, function(module) {
modules.push(module);
});
resumeBootstrapInternal();
};
}
var SNAKE_CASE_REGEXP = /[A-Z]/g;

View file

@ -658,7 +658,7 @@ describe('angular', function() {
var element = jqLite('<div>{{1+2}}</div>');
var injector = angular.bootstrap(element);
expect(injector).toBeDefined();
expect(element.data('$injector')).toBe(injector);
expect(element.injector()).toBe(injector);
dealoc(element);
});
@ -672,6 +672,78 @@ describe('angular', function() {
expect(element.html()).toBe('{{1+2}}');
dealoc(element);
});
describe('deferred bootstrap', function() {
var originalName = window.name,
element;
beforeEach(function() {
window.name = '';
element = jqLite('<div>{{1+2}}</div>');
});
afterEach(function() {
dealoc(element);
window.name = originalName;
});
it('should wait for extra modules', function() {
window.name = 'NG_DEFER_BOOTSTRAP!';
angular.bootstrap(element);
expect(element.html()).toBe('{{1+2}}');
angular.resumeBootstrap();
expect(element.html()).toBe('3');
expect(window.name).toEqual('');
});
it('should load extra modules', function() {
element = jqLite('<div>{{1+2}}</div>');
window.name = 'NG_DEFER_BOOTSTRAP!';
var bootstrapping = jasmine.createSpy('bootstrapping');
angular.bootstrap(element, [bootstrapping]);
expect(bootstrapping).not.toHaveBeenCalled();
expect(element.injector()).toBeUndefined();
angular.module('addedModule', []).value('foo', 'bar');
angular.resumeBootstrap(['addedModule']);
expect(bootstrapping).toHaveBeenCalledOnce();
expect(element.injector().get('foo')).toEqual('bar');
});
it('should not defer bootstrap without window.name cue', function() {
angular.bootstrap(element, []);
angular.module('addedModule', []).value('foo', 'bar');
expect(function() {
element.injector().get('foo');
}).toThrow('Unknown provider: fooProvider <- foo');
expect(element.injector().get('$http')).toBeDefined();
});
it('should restore the original window.name after bootstrap', function() {
window.name = 'NG_DEFER_BOOTSTRAP!my custom name';
angular.bootstrap(element);
expect(element.html()).toBe('{{1+2}}');
angular.resumeBootstrap();
expect(element.html()).toBe('3');
expect(window.name).toEqual('my custom name');
});
});
});