From ddb62feae6313b8dc56b50e9cad9e5151493fbd3 Mon Sep 17 00:00:00 2001 From: Shahar Talmi Date: Tue, 18 Aug 2015 01:31:41 +0300 Subject: [PATCH] feat(router): add reuse support for angular 1.x router Closes #3698 --- modules/angular1_router/build.js | 12 +++- modules/angular1_router/src/ng_outlet.js | 12 ++-- .../angular1_router/test/ng_outlet_spec.js | 70 +++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/modules/angular1_router/build.js b/modules/angular1_router/build.js index 62c790ec7c..cacaf86320 100644 --- a/modules/angular1_router/build.js +++ b/modules/angular1_router/build.js @@ -1,3 +1,5 @@ +'use strict'; + var fs = require('fs'); var ts = require('typescript'); @@ -58,6 +60,10 @@ function main() { "});", "}));", "}", + "if (constructor.$routeConfig) {", + "constructor.annotations = constructor.annotations || [];", + "constructor.annotations.push(new angular.annotations.RouteConfig(constructor.$routeConfig));", + "}", "if (constructor.annotations) {", "constructor.annotations.forEach(function(annotation) {", "if (annotation instanceof RouteConfig) {", @@ -70,7 +76,11 @@ function main() { "});", "var router = new RootRouter(registry, undefined, location, new Object());", - "$rootScope.$watch(function () { return $location.path(); }, function (path) { router.navigate(path); });", + "$rootScope.$watch(function () { return $location.path(); }, function (path) {", + "if (router.lastNavigationAttempt !== path) {", + "router.navigate(path);", + "}", + "});", "return router;" ].join('\n')); diff --git a/modules/angular1_router/src/ng_outlet.js b/modules/angular1_router/src/ng_outlet.js index 7598c8a9ee..3d170d5952 100644 --- a/modules/angular1_router/src/ng_outlet.js +++ b/modules/angular1_router/src/ng_outlet.js @@ -134,11 +134,14 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $ router.registerOutlet({ commit: function (instruction) { - var next; + var next = $q.when(true); var componentInstruction = instruction.component; if (componentInstruction.reuse) { - // todo(shahata): lifecycle - onReuse - next = $q.when(true); + var previousInstruction = currentInstruction; + currentInstruction = componentInstruction; + if (currentController.onReuse) { + next = $q.when(currentController.onReuse(currentInstruction, previousInstruction)); + } } else { var self = this; next = this.deactivate(instruction).then(function () { @@ -159,8 +162,9 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $ if (!currentInstruction || currentInstruction.componentType !== componentInstruction.componentType) { result = false; + } else if (currentController.canReuse) { + result = currentController.canReuse(componentInstruction, currentInstruction); } else { - // todo(shahata): lifecycle - canReuse result = componentInstruction === currentInstruction || angular.equals(componentInstruction.params, currentInstruction.params); } diff --git a/modules/angular1_router/test/ng_outlet_spec.js b/modules/angular1_router/test/ng_outlet_spec.js index cd89ffc005..ac9c977683 100644 --- a/modules/angular1_router/test/ng_outlet_spec.js +++ b/modules/angular1_router/test/ng_outlet_spec.js @@ -358,6 +358,76 @@ describe('ngOutlet', function () { }); + it('should reuse a component when the canReuse hook returns true', function () { + var log = []; + var cmpInstanceCount = 0; + + function ReuseCmp() { + cmpInstanceCount++; + this.canReuse = function () { + return true; + }; + this.onReuse = function (next, prev) { + log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath); + }; + } + ReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}]; + registerComponent('reuse', 'reuse {}', ReuseCmp); + + $router.config([ + { path: '/on-reuse/:number/...', component: ReuseCmp } + ]); + compile('outer {
}'); + + $router.navigate('/on-reuse/1/a'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {one} }'); + + $router.navigate('/on-reuse/2/b'); + $rootScope.$digest(); + expect(log).toEqual(['reuse: on-reuse/1 -> on-reuse/2']); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {two} }'); + }); + + + it('should not reuse a component when the canReuse hook returns false', function () { + var log = []; + var cmpInstanceCount = 0; + + function NeverReuseCmp() { + cmpInstanceCount++; + this.canReuse = function () { + return false; + }; + this.onReuse = function (next, prev) { + log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath); + }; + } + NeverReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}]; + registerComponent('reuse', 'reuse {}', NeverReuseCmp); + + $router.config([ + { path: '/never-reuse/:number/...', component: NeverReuseCmp } + ]); + compile('outer {
}'); + + $router.navigate('/never-reuse/1/a'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {one} }'); + + $router.navigate('/never-reuse/2/b'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(2); + expect(elt.text()).toBe('outer { reuse {two} }'); + }); + + it('should not activate a component when canActivate returns false', function () { var spy = jasmine.createSpy('activate'); var activate = registerComponent('activate', '', {