From 3f8b2858ac55058fc5a3eb67c372f8810f76eb7e Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Fri, 19 Feb 2016 21:13:52 +0200 Subject: [PATCH] fix(ngRoute): allow `ngView` to be included in an asynchronously loaded template During it's linking phase, `ngView` relies on the info provided in `$route.current` for instantiating the initial view. `$route.current` is set in the callback of a listener to `$locationChangeSuccess`, which is registered during the instantiation of the `$route` service. Thus, it is crucial that the `$route` service is instantiated before the initial `$locationChangeSuccess` is fired. Since `ngView` declares `$route` as a dependency, the service is instantiated in time if `ngView` is present during the initial load of the page. Yet, in cases where `ngView` is included in a template that is loaded asynchronously (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. This commit fixes it, by always instantiating `$route` up front, during the initialization phase. Fixes #1213 Fixes #6812 --- src/ngRoute/route.js | 6 +++++- test/ngRoute/directive/ngViewSpec.js | 31 ++++++++++++++++++++++++++++ test/ngRoute/routeSpec.js | 15 ++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 149d0bb96a69..7eca8a888dd8 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -17,7 +17,11 @@ */ /* global -ngRouteModule */ var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), + provider('$route', $RouteProvider). + // Ensure `$route` will be instantiated in time to capture the initial + // `$locationChangeSuccess` event. This is necessary in case `ngView` is + // included in an asynchronously loaded template. + run(['$route', angular.noop]), $routeMinErr = angular.$$minErr('ngRoute'); /** diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index bb5a64951fa7..8cc3d5195465 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 889489a0d63d..4c6f6c1541a7 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -23,6 +23,21 @@ describe('$route', function() { dealoc(element); }); + it('should be loaded upon initial load (even if `ngView` is loaded async)', function() { + module(function($routeProvider) { + $routeProvider.when('/', {template: 'Hello, world !'}); + }); + + inject(function($location, $rootScope) { + $location.path('/'); + $rootScope.$digest(); + }); + + inject(function($route) { + expect($route.current).toBeDefined(); + }); + }); + it('should allow cancellation via $locationChangeStart via $routeChangeStart', function() { module(function($routeProvider) { $routeProvider.when('/Edit', {