diff --git a/modules/@angular/router/package.json b/modules/@angular/router/package.json index c7e9ac1317..c14e4a0690 100644 --- a/modules/@angular/router/package.json +++ b/modules/@angular/router/package.json @@ -33,11 +33,11 @@ }, "homepage": "https://github.com/angular/vladivostok#readme", "dependencies": { - "@angular/common": "^2.0.0-rc.1", - "@angular/compiler": "^2.0.0-rc.1", - "@angular/core": "^2.0.0-rc.1", - "@angular/platform-browser": "^2.0.0-rc.1", - "@angular/platform-browser-dynamic": "^2.0.0-rc.1", + "@angular/common": "^2.0.0-rc.2", + "@angular/compiler": "^2.0.0-rc.2", + "@angular/core": "^2.0.0-rc.2", + "@angular/platform-browser": "^2.0.0-rc.2", + "@angular/platform-browser-dynamic": "^2.0.0-rc.2", "rxjs": "5.0.0-beta.6" }, "devDependencies": { diff --git a/modules/@angular/router/src/directives/router_link.ts b/modules/@angular/router/src/directives/router_link.ts index e4313134ba..a22737df2f 100644 --- a/modules/@angular/router/src/directives/router_link.ts +++ b/modules/@angular/router/src/directives/router_link.ts @@ -1,4 +1,5 @@ import {Directive, HostBinding, HostListener, Input, OnChanges} from '@angular/core'; +import {LocationStrategy} from '@angular/common'; import {Router} from '../router'; import {ActivatedRoute} from '../router_state'; @@ -46,7 +47,7 @@ export class RouterLink implements OnChanges { /** * @internal */ - constructor(private router: Router, private route: ActivatedRoute) {} + constructor(private router: Router, private route: ActivatedRoute, private locationStrategy: LocationStrategy) {} @Input() set routerLink(data: any[]|string) { @@ -59,14 +60,18 @@ export class RouterLink implements OnChanges { ngOnChanges(changes: {}): any { this.updateTargetUrlAndHref(); } - @HostListener('click') - onClick(): boolean { - // If no target, or if target is _self, prevent default browser behavior - if (!(typeof this.target === 'string') || this.target == '_self') { - this.router.navigateByUrl(this.urlTree); - return false; + @HostListener("click", ["$event.button", "$event.ctrlKey", "$event.metaKey"]) + onClick(button: number, ctrlKey: boolean, metaKey: boolean): boolean { + if (button !== 0 || ctrlKey || metaKey) { + return true; } - return true; + + if (typeof this.target === 'string' && this.target != '_self') { + return true; + } + + this.router.navigateByUrl(this.urlTree); + return false; } private updateTargetUrlAndHref(): void { @@ -74,7 +79,7 @@ export class RouterLink implements OnChanges { this.commands, {relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment}); if (this.urlTree) { - this.href = this.router.serializeUrl(this.urlTree); + this.href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.urlTree)); } } } diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 656cd56d97..689a624a28 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -245,10 +245,10 @@ export class Router { */ parseUrl(url: string): UrlTree { return this.urlSerializer.parse(url); } - private scheduleNavigation(url: UrlTree, pop: boolean): Promise { + private scheduleNavigation(url: UrlTree, preventPushState: boolean): Promise { const id = ++this.navigationId; this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url))); - return Promise.resolve().then((_) => this.runNavigate(url, false, id)); + return Promise.resolve().then((_) => this.runNavigate(url, preventPushState, id)); } private setUpLocationChangeListener(): void { @@ -257,7 +257,7 @@ export class Router { }); } - private runNavigate(url: UrlTree, pop: boolean, id: number): Promise { + private runNavigate(url: UrlTree, preventPushState: boolean, id: number): Promise { if (id !== this.navigationId) { this.location.go(this.urlSerializer.serialize(this.currentUrlTree)); this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url))); @@ -295,7 +295,6 @@ export class Router { }) .forEach((shouldActivate) => { if (!shouldActivate || id !== this.navigationId) { - this.location.go(this.urlSerializer.serialize(this.currentUrlTree)); this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url))); return Promise.resolve(false); } @@ -304,8 +303,13 @@ export class Router { this.currentUrlTree = updatedUrl; this.currentRouterState = state; - if (!pop) { - this.location.go(this.urlSerializer.serialize(updatedUrl)); + if (!preventPushState) { + let path = this.urlSerializer.serialize(updatedUrl); + if (this.location.isCurrentPathEqualTo(path)) { + this.location.replaceState(path); + } else { + this.location.go(path); + } } }) .then( diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index 6614582c31..ee7560a372 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -18,7 +18,7 @@ import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing' import { ComponentResolver } from '@angular/core'; import { SpyLocation } from '@angular/common/testing'; import { UrlSerializer, DefaultUrlSerializer, RouterOutletMap, Router, ActivatedRoute, ROUTER_DIRECTIVES, Params, - RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError, RoutesRecognized, RouterConfig } from '../src/index'; + RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError, RoutesRecognized, RouterConfig } from '../src/index'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import {of} from 'rxjs/observable/of'; @@ -79,7 +79,7 @@ describe("Integration", () => { expect(location.path()).toEqual('/team/33'); }))); - xit('should navigate back and forward', + it('should navigate back and forward', fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { const fixture = tcb.createFakeAsync(RootCmp); advance(fixture); @@ -348,6 +348,32 @@ describe("Integration", () => { [NavigationEnd, '/user/fedor'] ]); }))); + + it('should replace state when path is equal to current path', + fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { + const fixture = tcb.createFakeAsync(RootCmp); + advance(fixture); + + router.resetConfig([ + { path: 'team/:id', component: TeamCmp, children: [ + { path: 'simple', component: SimpleCmp }, + { path: 'user/:name', component: UserCmp } + ] } + ]); + + router.navigateByUrl('/team/33/simple'); + advance(fixture); + + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); + + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); + + location.back(); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); + }))); describe("router links", () => { it("should support string router links",