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

feat(ngRoute): allow ngView to be included in an asynchronously loaded template #14893

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions src/ngRoute/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,10 +23,17 @@ var isObject;
*
* <div doc-module-components="ngRoute"></div>
*/
/* 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
Expand All @@ -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);
Expand Down Expand Up @@ -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**:<br />
* 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',
Expand Down Expand Up @@ -791,3 +841,11 @@ function $RouteProvider() {
}
}];
}

instantiateRoute.$inject = ['$injector'];
function instantiateRoute($injector) {
if (isEagerInstantiationEnabled) {
// Instantiate `$route`
$injector.get('$route');
}
}
31 changes: 31 additions & 0 deletions test/ngRoute/directive/ngViewSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<ng-view></ng-view>');
};
});

$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('<async-view></async-view>')($rootScope);
$rootScope.$digest();
$timeout.flush(500);

expect(elem.text()).toBe('Hello, world!');

dealoc(elem);
})
);
});
53 changes: 53 additions & 0 deletions test/ngRoute/routeSpec.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down