diff --git a/src/Angular.js b/src/Angular.js index 52e74cbf396c..e745e55498e9 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1408,8 +1408,12 @@ function bootstrap(element, modules, config) { forEach(extraModules, function(module) { modules.push(module); }); - doBootstrap(); + return doBootstrap(); }; + + if (isFunction(angular.resumeDeferredBootstrap)) { + angular.resumeDeferredBootstrap(); + } } /** diff --git a/src/ngScenario/Application.js b/src/ngScenario/Application.js index 80dd15742f3b..e7d5bb86c956 100644 --- a/src/ngScenario/Application.js +++ b/src/ngScenario/Application.js @@ -68,19 +68,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF try { var $window = self.getWindow_(); - if ($window.angular) { - // Disable animations - $window.angular.resumeBootstrap([['$provide', function($provide) { - return ['$animate', function($animate) { - $animate.enabled(false); - }]; - }]]); + if (!$window.angular) { + self.executeAction(loadFn); + return; + } + + if (!$window.angular.resumeBootstrap) { + $window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap; + } else { + resumeDeferredBootstrap(); } - self.executeAction(loadFn); } catch (e) { errorFn(e); } + + function resumeDeferredBootstrap() { + // Disable animations + var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) { + return ['$animate', function($animate) { + $animate.enabled(false); + }]; + }]]); + self.rootElement = $injector.get('$rootElement')[0]; + self.executeAction(loadFn); + } }).attr('src', url); // for IE compatibility set the name *after* setting the frame url @@ -105,7 +117,15 @@ angular.scenario.Application.prototype.executeAction = function(action) { if (!$window.angular) { return action.call(this, $window, _jQuery($window.document)); } - angularInit($window.document, function(element) { + + if (!!this.rootElement) { + executeWithElement(this.rootElement); + } + else { + angularInit($window.document, angular.bind(this, executeWithElement)); + } + + function executeWithElement(element) { var $injector = $window.angular.element(element).injector(); var $element = _jQuery(element); @@ -118,5 +138,5 @@ angular.scenario.Application.prototype.executeAction = function(action) { action.call(self, $window, $element); }); }); - }); + } }; diff --git a/test/AngularSpec.js b/test/AngularSpec.js index e63789100947..a45ee2c258c9 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1081,6 +1081,26 @@ describe('angular', function() { window.name = originalName; }); + it('should provide injector for deferred bootstrap', function() { + var injector; + window.name = 'NG_DEFER_BOOTSTRAP!'; + + injector = angular.bootstrap(element); + expect(injector).toBeUndefined(); + + injector = angular.resumeBootstrap(); + expect(injector).toBeDefined(); + }); + + it('should resume deferred bootstrap, if defined', function() { + var injector; + window.name = 'NG_DEFER_BOOTSTRAP!'; + + angular.resumeDeferredBootstrap = noop; + var spy = spyOn(angular, "resumeDeferredBootstrap"); + injector = angular.bootstrap(element); + expect(spy).toHaveBeenCalled(); + }); it('should wait for extra modules', function() { window.name = 'NG_DEFER_BOOTSTRAP!'; diff --git a/test/ngScenario/ApplicationSpec.js b/test/ngScenario/ApplicationSpec.js index b86a4e5eccdc..d120f21a12ce 100644 --- a/test/ngScenario/ApplicationSpec.js +++ b/test/ngScenario/ApplicationSpec.js @@ -118,6 +118,75 @@ describe('angular.scenario.Application', function() { expect(called).toBeTruthy(); }); + it('should set rootElement when navigateTo instigates bootstrap', inject(function($injector, $browser) { + var called; + var testWindow = { + document: jqLite('<div class="test-foo"></div>')[0], + angular: { + element: jqLite, + service: {}, + resumeBootstrap: noop + } + }; + jqLite(testWindow.document).data('$injector', $injector); + var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector); + + var injectorGet = $injector.get; + spyOn($injector, 'get').andCallFake(function(name) { + switch (name) { + case "$rootElement": return jqLite(testWindow.document); + default: return injectorGet(name); + } + }); + + app.getWindow_ = function() { + return testWindow; + }; + app.navigateTo('http://localhost/', noop); + callLoadHandlers(app); + expect(app.rootElement).toBe(testWindow.document); + expect(resumeBootstrapSpy).toHaveBeenCalled(); + dealoc(testWindow.document); + })); + + it('should set setup resumeDeferredBootstrap if resumeBootstrap is not yet defined', inject(function($injector, $browser) { + var called; + var testWindow = { + document: jqLite('<div class="test-foo"></div>')[0], + angular: { + element: jqLite, + service: {}, + resumeBootstrap: null + } + }; + jqLite(testWindow.document).data('$injector', $injector); + + var injectorGet = $injector.get; + var injectorSpy = spyOn($injector, 'get').andCallFake(function(name) { + switch (name) { + case "$rootElement": return jqLite(testWindow.document); + default: return injectorGet(name); + } + }); + + app.getWindow_ = function() { + return testWindow; + }; + app.navigateTo('http://localhost/', noop); + expect(testWindow.angular.resumeDeferredBootstrap).toBeUndefined(); + callLoadHandlers(app); + expect(testWindow.angular.resumeDeferredBootstrap).toBeDefined(); + expect(app.rootElement).toBeUndefined; + expect(injectorSpy).not.toHaveBeenCalled(); + + var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector); + testWindow.angular.resumeDeferredBootstrap(); + expect(app.rootElement).toBe(testWindow.document); + expect(resumeBootstrapSpy).toHaveBeenCalled(); + expect(injectorSpy).toHaveBeenCalledWith("$rootElement"); + dealoc(testWindow.document); + })); + it('should wait for pending requests in executeAction', inject(function($injector, $browser) { var called, polled; var handlers = []; @@ -144,4 +213,32 @@ describe('angular.scenario.Application', function() { handlers[0](); dealoc(testWindow.document); })); + + it('should allow explicit rootElement', inject(function($injector, $browser) { + var called, polled; + var handlers = []; + var testWindow = { + document: jqLite('<div class="test-foo"></div>')[0], + angular: { + element: jqLite, + service: {} + } + }; + $browser.notifyWhenNoOutstandingRequests = function(fn) { + handlers.push(fn); + }; + app.rootElement = testWindow.document; + jqLite(testWindow.document).data('$injector', $injector); + app.getWindow_ = function() { + return testWindow; + }; + app.executeAction(function($window, $document) { + expect($window).toEqual(testWindow); + expect($document).toBeDefined(); + expect($document[0].className).toEqual('test-foo'); + }); + expect(handlers.length).toEqual(1); + handlers[0](); + dealoc(testWindow.document); + })); });