diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 4ead323e8baf..8dcb2bd718de 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -2,10 +2,11 @@ /* global shallowCopy: false */ -// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`). +// `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`). // They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available. var isArray; var isObject; +var isDefined; /** * @ngdoc module @@ -22,10 +23,17 @@ var isObject; * *
*/ - /* global -ngRouteModule */ -var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), - $routeMinErr = angular.$$minErr('ngRoute'); +/* global -ngRouteModule */ +var ngRouteModule = angular. + module('ngRoute', []). + provider('$route', $RouteProvider). + // Ensure `$route` will be instantiated in time to capture the initial `$locationChangeSuccess` + // event (unless explicitly disabled). This is necessary in case `ngView` is included in an + // asynchronously loaded template. + run(instantiateRoute); +var $routeMinErr = angular.$$minErr('ngRoute'); +var isEagerInstantiationEnabled; + /** * @ngdoc provider @@ -44,6 +52,7 @@ var ngRouteModule = angular.module('ngRoute', ['ng']). function $RouteProvider() { isArray = angular.isArray; isObject = angular.isObject; + isDefined = angular.isDefined; function inherit(parent, extra) { return angular.extend(Object.create(parent), extra); @@ -287,6 +296,47 @@ function $RouteProvider() { return this; }; + /** + * @ngdoc method + * @name $routeProvider#eagerInstantiationEnabled + * @kind function + * + * @description + * Call this method as a setter to enable/disable eager instantiation of the + * {@link ngRoute.$route $route} service upon application bootstrap. You can also call it as a + * getter (i.e. without any arguments) to get the current value of the + * `eagerInstantiationEnabled` flag. + * + * Instantiating `$route` early is necessary for capturing the initial + * {@link ng.$location#$locationChangeStart $locationChangeStart} event and navigating to the + * appropriate route. Usually, `$route` is instantiated in time by the + * {@link ngRoute.ngView ngView} directive. Yet, in cases where `ngView` is included in an + * asynchronously loaded template (e.g. in another directive's template), the directive factory + * might not be called soon enough for `$route` to be instantiated _before_ the initial + * `$locationChangeSuccess` event is fired. Eager instantiation ensures that `$route` is always + * instantiated in time, regardless of when `ngView` will be loaded. + * + * The default value is true. + * + * **Note**:
+ * You may want to disable the default behavior when unit-testing modules that depend on + * `ngRoute`, in order to avoid an unexpected request for the default route's template. + * + * @param {boolean=} enabled - If provided, update the internal `eagerInstantiationEnabled` flag. + * + * @returns {*} The current value of the `eagerInstantiationEnabled` flag if used as a getter or + * itself (for chaining) if used as a setter. + */ + isEagerInstantiationEnabled = true; + this.eagerInstantiationEnabled = function eagerInstantiationEnabled(enabled) { + if (isDefined(enabled)) { + isEagerInstantiationEnabled = enabled; + return this; + } + + return isEagerInstantiationEnabled; + }; + this.$get = ['$rootScope', '$location', @@ -791,3 +841,11 @@ function $RouteProvider() { } }]; } + +instantiateRoute.$inject = ['$injector']; +function instantiateRoute($injector) { + if (isEagerInstantiationEnabled) { + // Instantiate `$route` + $injector.get('$route'); + } +} diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index daab5f26c987..66d2653108ce 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -1027,3 +1027,34 @@ describe('ngView animations', function() { )); }); }); + +describe('ngView in async template', function() { + beforeEach(module('ngRoute')); + beforeEach(module(function($compileProvider, $provide, $routeProvider) { + $compileProvider.directive('asyncView', function() { + return {templateUrl: 'async-view.html'}; + }); + + $provide.decorator('$templateRequest', function($timeout) { + return function() { + return $timeout(angular.identity, 500, false, ''); + }; + }); + + $routeProvider.when('/', {template: 'Hello, world!'}); + })); + + + it('should work correctly upon initial page load', + // Injecting `$location` here is necessary, so that it gets instantiated early + inject(function($compile, $location, $rootScope, $timeout) { + var elem = $compile('')($rootScope); + $rootScope.$digest(); + $timeout.flush(500); + + expect(elem.text()).toBe('Hello, world!'); + + dealoc(elem); + }) + ); +}); diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 1d6037a84f9d..5a0780f5efbf 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -1,5 +1,58 @@ 'use strict'; +describe('$routeProvider', function() { + var $routeProvider; + + beforeEach(module('ngRoute')); + beforeEach(module(function(_$routeProvider_) { + $routeProvider = _$routeProvider_; + $routeProvider.when('/foo', {template: 'Hello, world!'}); + })); + + + it('should support enabling/disabling automatic instantiation upon initial load', + inject(function() { + expect($routeProvider.eagerInstantiationEnabled(true)).toBe($routeProvider); + expect($routeProvider.eagerInstantiationEnabled()).toBe(true); + + expect($routeProvider.eagerInstantiationEnabled(false)).toBe($routeProvider); + expect($routeProvider.eagerInstantiationEnabled()).toBe(false); + + expect($routeProvider.eagerInstantiationEnabled(true)).toBe($routeProvider); + expect($routeProvider.eagerInstantiationEnabled()).toBe(true); + }) + ); + + + it('should automatically instantiate `$route` upon initial load', function() { + inject(function($location, $rootScope) { + $location.path('/foo'); + $rootScope.$digest(); + }); + + inject(function($route) { + expect($route.current).toBeDefined(); + }); + }); + + + it('should not automatically instantiate `$route` if disabled', function() { + module(function($routeProvider) { + $routeProvider.eagerInstantiationEnabled(false); + }); + + inject(function($location, $rootScope) { + $location.path('/foo'); + $rootScope.$digest(); + }); + + inject(function($route) { + expect($route.current).toBeUndefined(); + }); + }); +}); + + describe('$route', function() { var $httpBackend, element;