diff --git a/modules/angular1_router/lib/facades.es5 b/modules/angular1_router/lib/facades.es5 index f419cdef5ff5..b84d86bf361a 100644 --- a/modules/angular1_router/lib/facades.es5 +++ b/modules/angular1_router/lib/facades.es5 @@ -303,8 +303,8 @@ Location.prototype.subscribe = function () { //TODO: implement }; Location.prototype.path = function () { - return $location.path(); + return $location.url(); }; -Location.prototype.go = function (url) { - return $location.path(url); +Location.prototype.go = function (path, query) { + return $location.url(path + query); }; diff --git a/modules/angular1_router/src/module_template.js b/modules/angular1_router/src/module_template.js index 43415af4d5b3..0139664bc752 100644 --- a/modules/angular1_router/src/module_template.js +++ b/modules/angular1_router/src/module_template.js @@ -57,7 +57,7 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc }); var router = new RootRouter(registry, location, $routerRootComponent); - $rootScope.$watch(function () { return $location.path(); }, function (path) { + $rootScope.$watch(function () { return $location.url(); }, function (path) { if (router.lastNavigationAttempt !== path) { router.navigateByUrl(path); } diff --git a/modules/angular1_router/src/ng_outlet.ts b/modules/angular1_router/src/ng_outlet.ts index 6184f7655a3e..62bfe20635fd 100644 --- a/modules/angular1_router/src/ng_outlet.ts +++ b/modules/angular1_router/src/ng_outlet.ts @@ -155,10 +155,12 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) { } this.controller.$$routeParams = instruction.params; - this.controller.$$template = '
'; + this.controller.$$template = + '<' + dashCase(componentName) + ' router="$$router">'; this.controller.$$router = this.router.childRouter(instruction.componentType); let newScope = scope.$new(); + newScope.$$router = this.controller.$$router; let clone = $transclude(newScope, clone => { $animate.enter(clone, null, this.currentElement || element); diff --git a/modules/angular1_router/test/integration/navigation_spec.js b/modules/angular1_router/test/integration/navigation_spec.js index 3d230c4b1290..70bcf2eeee04 100644 --- a/modules/angular1_router/test/integration/navigation_spec.js +++ b/modules/angular1_router/test/integration/navigation_spec.js @@ -21,17 +21,29 @@ describe('navigation', function () { $router = _$router_; }); - registerComponent('userCmp', { + registerDirective('userCmp', { template: '
hello {{userCmp.$routeParams.name}}
' }); - registerComponent('oneCmp', { + registerDirective('oneCmp', { template: '
{{oneCmp.number}}
', controller: function () {this.number = 'one'} }); - registerComponent('twoCmp', { + registerDirective('twoCmp', { template: '
{{twoCmp.number}}
', controller: function () {this.number = 'two'} }); + registerComponent('threeCmp', { + template: '
{{$ctrl.number}}
', + controller: function () {this.number = 'three'} + }); + registerComponent('getParams', { + template: '
{{$ctrl.params.x}}
', + controller: function () { + this.$routerOnActivate = function(next) { + this.params = next.params; + }; + } + }) }); it('should work in a simple case', function () { @@ -47,6 +59,21 @@ describe('navigation', function () { expect(elt.text()).toBe('one'); }); + + it('should work with components created by the `mod.component()` helper', function () { + compile(''); + + $router.config([ + { path: '/', component: 'threeCmp' } + ]); + + $router.navigateByUrl('/'); + $rootScope.$digest(); + + expect(elt.text()).toBe('three'); + }); + + it('should navigate between components with different parameters', function () { $router.config([ { path: '/user/:name', component: 'userCmp' } @@ -68,7 +95,7 @@ describe('navigation', function () { function ParentController() { instanceCount += 1; } - registerComponent('parentCmp', { + registerDirective('parentCmp', { template: 'parent { }', $routeConfig: [ { path: '/user/:name', component: 'userCmp' } @@ -94,7 +121,7 @@ describe('navigation', function () { it('should work with nested outlets', function () { - registerComponent('childCmp', { + registerDirective('childCmp', { template: '
inner {
}
', $routeConfig: [ { path: '/b', component: 'oneCmp' } @@ -112,9 +139,29 @@ describe('navigation', function () { expect(elt.text()).toBe('outer { inner { one } }'); }); + it('should work when parent route has empty path', inject(function ($location) { + registerComponent('childCmp', { + template: '
inner {
}
', + $routeConfig: [ + { path: '/b', component: 'oneCmp' } + ] + }); + + $router.config([ + { path: '/...', component: 'childCmp' } + ]); + compile('
outer {
}
'); + + $router.navigateByUrl('/b'); + $rootScope.$digest(); + + expect(elt.text()).toBe('outer { inner { one } }'); + expect($location.path()).toBe('/b'); + })); + it('should work with recursive nested outlets', function () { - registerComponent('recurCmp', { + registerDirective('recurCmp', { template: '
recur {
}
', $routeConfig: [ { path: '/recur', component: 'recurCmp' }, @@ -147,6 +194,21 @@ describe('navigation', function () { })); + it('should pass through query terms to the location', inject(function ($location) { + $router.config([ + { path: '/user', component: 'userCmp' } + ]); + + compile('
'); + + $router.navigateByUrl('/user?x=y'); + $rootScope.$digest(); + + expect($location.path()).toBe('/user'); + expect($location.search()).toEqual({ x: 'y'}); + })); + + it('should change location to the canonical route', inject(function ($location) { compile('
'); @@ -163,7 +225,7 @@ describe('navigation', function () { it('should change location to the canonical route with nested components', inject(function ($location) { - registerComponent('childRouter', { + registerDirective('childRouter', { template: '
inner {
}
', $routeConfig: [ { path: '/new-child', component: 'oneCmp', name: 'NewChild'}, @@ -206,9 +268,22 @@ describe('navigation', function () { })); + it('should navigate when the location query changes', inject(function ($location) { + $router.config([ + { path: '/get/params', component: 'getParams' } + ]); + compile('
'); + + $location.url('/get/params?x=y'); + $rootScope.$digest(); + + expect(elt.text()).toBe('y'); + })); + + it('should expose a "navigating" property on $router', inject(function ($q) { var defer; - registerComponent('pendingActivate', { + registerDirective('pendingActivate', { $canActivate: function () { defer = $q.defer(); return defer.promise; @@ -227,31 +302,26 @@ describe('navigation', function () { expect($router.navigating).toBe(false); })); - function registerComponent(name, options) { - var controller = options.controller || function () {}; - - ['$routerOnActivate', '$routerOnDeactivate', '$routerOnReuse', '$routerCanReuse', '$routerCanDeactivate'].forEach(function (hookName) { - if (options[hookName]) { - controller.prototype[hookName] = options[hookName]; - } - }); - + function registerDirective(name, options) { function factory() { return { template: options.template || '', controllerAs: name, - controller: controller + controller: getController(options) }; } + applyStaticProperties(factory, options); + $compileProvider.directive(name, factory); + } - if (options.$canActivate) { - factory.$canActivate = options.$canActivate; - } - if (options.$routeConfig) { - factory.$routeConfig = options.$routeConfig; - } + function registerComponent(name, options) { - $compileProvider.directive(name, factory); + var definition = { + template: options.template || '', + controller: getController(options), + } + applyStaticProperties(definition, options); + $compileProvider.component(name, definition); } function compile(template) { @@ -259,4 +329,27 @@ describe('navigation', function () { $rootScope.$digest(); return elt; } + + function getController(options) { + var controller = options.controller || function () {}; + [ + '$routerOnActivate', '$routerOnDeactivate', + '$routerOnReuse', '$routerCanReuse', + '$routerCanDeactivate' + ].forEach(function (hookName) { + if (options[hookName]) { + controller.prototype[hookName] = options[hookName]; + } + }); + return controller; + } + + function applyStaticProperties(target, options) { + ['$canActivate', '$routeConfig'].forEach(function(property) { + if (options[property]) { + target[property] = options[property]; + } + }); + } }); + diff --git a/modules/angular1_router/test/integration/router_spec.js b/modules/angular1_router/test/integration/router_spec.js index c302c21cea62..bea802f366b6 100644 --- a/modules/angular1_router/test/integration/router_spec.js +++ b/modules/angular1_router/test/integration/router_spec.js @@ -44,31 +44,57 @@ describe('router', function () { expect(elt.text()).toBe('Home'); })); - function registerComponent(name, options) { - var controller = options.controller || function () {}; - ['$onActivate', '$onDeactivate', '$onReuse', '$canReuse', '$canDeactivate'].forEach(function (hookName) { - if (options[hookName]) { - controller.prototype[hookName] = options[hookName]; - } + it('should bind the component to the current router', inject(function($location) { + var router; + registerComponent('homeCmp', { + bindings: { router: '=' }, + controller: function($scope, $element) { + this.$routerOnActivate = function() { + router = this.router; + }; + }, + template: 'Home' }); + registerComponent('app', { + template: '
', + $routeConfig: [ + { path: '/', component: 'homeCmp' } + ] + }); + + compile(''); + + $location.path('/'); + $rootScope.$digest(); + var homeElement = elt.find('home-cmp'); + expect(homeElement.text()).toBe('Home'); + expect(homeElement.isolateScope().$ctrl.router).toBeDefined(); + expect(router).toBeDefined(); + })); + + function registerDirective(name, options) { function factory() { return { template: options.template || '', controllerAs: name, - controller: controller + controller: getController(options) }; } + applyStaticProperties(factory, options); + $compileProvider.directive(name, factory); + } - if (options.$canActivate) { - factory.$canActivate = options.$canActivate; - } - if (options.$routeConfig) { - factory.$routeConfig = options.$routeConfig; - } + function registerComponent(name, options) { - $compileProvider.directive(name, factory); + var definition = { + bindings: options.bindings, + template: options.template || '', + controller: getController(options), + } + applyStaticProperties(definition, options); + $compileProvider.component(name, definition); } function compile(template) { @@ -76,4 +102,26 @@ describe('router', function () { $rootScope.$digest(); return elt; } -}); + + function getController(options) { + var controller = options.controller || function () {}; + [ + '$routerOnActivate', '$routerOnDeactivate', + '$routerOnReuse', '$routerCanReuse', + '$routerCanDeactivate' + ].forEach(function (hookName) { + if (options[hookName]) { + controller.prototype[hookName] = options[hookName]; + } + }); + return controller; + } + + function applyStaticProperties(target, options) { + ['$canActivate', '$routeConfig'].forEach(function(property) { + if (options[property]) { + target[property] = options[property]; + } + }); + } +}); \ No newline at end of file diff --git a/modules/angular2/src/router/router.ts b/modules/angular2/src/router/router.ts index a1bd9b041606..d0f2ce3d16aa 100644 --- a/modules/angular2/src/router/router.ts +++ b/modules/angular2/src/router/router.ts @@ -438,7 +438,7 @@ export class RootRouter extends Router { } var emitPath = instruction.toUrlPath(); var emitQuery = instruction.toUrlQuery(); - if (emitPath.length > 0) { + if (emitPath.length > 0 && emitPath[0] != '/') { emitPath = '/' + emitPath; } @@ -465,7 +465,7 @@ export class RootRouter extends Router { commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise { var emitPath = instruction.toUrlPath(); var emitQuery = instruction.toUrlQuery(); - if (emitPath.length > 0) { + if (emitPath.length > 0 && emitPath[0] != '/') { emitPath = '/' + emitPath; } var promise = super.commit(instruction); diff --git a/modules/angular2/test/router/integration/navigation_spec.ts b/modules/angular2/test/router/integration/navigation_spec.ts index 8e649ec9c26e..78c07cfb7d02 100644 --- a/modules/angular2/test/router/integration/navigation_spec.ts +++ b/modules/angular2/test/router/integration/navigation_spec.ts @@ -105,6 +105,20 @@ export function main() { }); })); + it('should navigate to child routes when the root component has an empty path', + inject([AsyncTestCompleter, Location], (async, location) => { + compile(tcb, 'outer { }') + .then((rtc) => {fixture = rtc}) + .then((_) => rtr.config([new Route({path: '/...', component: ParentCmp})])) + .then((_) => rtr.navigateByUrl('/b')) + .then((_) => { + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }'); + expect(location.urlChanges).toEqual(['/b']); + async.done(); + }); + })); + it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => { compile(tcb, 'outer { }') .then((rtc) => {fixture = rtc}) diff --git a/npm-shrinkwrap.clean.json b/npm-shrinkwrap.clean.json index 2e81c5571242..fccea7ba8bab 100644 --- a/npm-shrinkwrap.clean.json +++ b/npm-shrinkwrap.clean.json @@ -46,13 +46,13 @@ "version": "1.0.0" }, "angular": { - "version": "1.4.8" + "version": "1.5.0" }, "angular-animate": { - "version": "1.4.8" + "version": "1.5.0" }, "angular-mocks": { - "version": "1.4.8" + "version": "1.5.0" }, "ansi": { "version": "0.3.0" @@ -5828,5 +5828,5 @@ } }, "name": "angular-srcs", - "version": "2.0.0-beta.2" + "version": "2.0.0-beta.3" } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index dbcceace50b0..def5bc54f362 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "2.0.0-beta.2", + "version": "2.0.0-beta.3", "dependencies": { "abbrev": { "version": "1.0.7", @@ -74,19 +74,19 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" }, "angular": { - "version": "1.4.8", - "from": "angular@>=1.4.7 <2.0.0", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.4.8.tgz" + "version": "1.5.0", + "from": "angular@1.5.0", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.5.0.tgz" }, "angular-animate": { - "version": "1.4.8", - "from": "angular-animate@>=1.4.7 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.4.8.tgz" + "version": "1.5.0", + "from": "angular-animate@1.5.0", + "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.5.0.tgz" }, "angular-mocks": { - "version": "1.4.8", - "from": "angular-mocks@>=1.4.7 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.4.8.tgz" + "version": "1.5.0", + "from": "angular-mocks@1.5.0", + "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.5.0.tgz" }, "ansi": { "version": "0.3.0", @@ -7668,7 +7668,7 @@ }, "rxjs": { "version": "5.0.0-beta.0", - "from": "rxjs@5.0.0-beta.0", + "from": "https://registry.npmjs.org/rxjs/-/rxjs-5.0.0-beta.0.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.0.0-beta.0.tgz" }, "sass-graph": { @@ -8519,29 +8519,29 @@ }, "ts2dart": { "version": "0.7.22", - "from": "ts2dart@>=0.7.22 <0.8.0", + "from": "https://registry.npmjs.org/ts2dart/-/ts2dart-0.7.22.tgz", "resolved": "https://registry.npmjs.org/ts2dart/-/ts2dart-0.7.22.tgz", "dependencies": { "source-map": { "version": "0.4.4", - "from": "source-map@>=0.4.2 <0.5.0", + "from": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" }, "source-map-support": { "version": "0.3.3", - "from": "source-map-support@>=0.3.1 <0.4.0", + "from": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.3.3.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.3.3.tgz", "dependencies": { "source-map": { "version": "0.1.32", - "from": "source-map@0.1.32", + "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz" } } }, "typescript": { "version": "1.7.3", - "from": "typescript@1.7.3", + "from": "https://registry.npmjs.org/typescript/-/typescript-1.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-1.7.3.tgz" } } @@ -9292,7 +9292,7 @@ }, "zone.js": { "version": "0.5.13", - "from": "zone.js@0.5.13", + "from": "https://registry.npmjs.org/zone.js/-/zone.js-0.5.13.tgz", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.5.13.tgz" } } diff --git a/package.json b/package.json index 93c8e91f1878..e659aa913668 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,9 @@ "zone.js": "0.5.13" }, "devDependencies": { - "angular": "^1.4.7", - "angular-animate": "^1.4.7", - "angular-mocks": "^1.4.7", + "angular": "^1.5.0", + "angular-animate": "^1.5.0", + "angular-mocks": "^1.5.0", "base64-js": "^0.0.8", "bower": "^1.3.12", "broccoli": "^0.16.9",