diff --git a/modules/@angular/router/index.ts b/modules/@angular/router/index.ts index 07f6108ecf..1e2343aabb 100644 --- a/modules/@angular/router/index.ts +++ b/modules/@angular/router/index.ts @@ -4,4 +4,6 @@ export { RouterState, ActivatedRoute } from './src/router_state'; export { RouterOutletMap } from './src/router_outlet_map'; import { RouterOutlet } from './src/directives/router_outlet'; -export const ROUTER_DIRECTIVES = [RouterOutlet]; \ No newline at end of file +import { RouterLink } from './src/directives/router_link'; + +export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink]; \ No newline at end of file diff --git a/modules/@angular/router/src/directives/router_link.ts b/modules/@angular/router/src/directives/router_link.ts new file mode 100644 index 0000000000..add7b3a9a0 --- /dev/null +++ b/modules/@angular/router/src/directives/router_link.ts @@ -0,0 +1,73 @@ +import { + Directive, + HostListener, + HostBinding, + Input +} from '@angular/core'; +import {Router} from '../router'; + +/** + * The RouterLink directive lets you link to specific parts of your app. + * + * Consider the following route configuration: + + * ``` + * [{ name: 'user', path: '/user', component: UserCmp }] + * ``` + * + * When linking to this `User` route, you can write: + * + * ``` + * link to user component + * ``` + * + * RouterLink expects the value to be an array of path segments, followed by the params + * for that level of routing. For instance `['/team', {teamId: 1}, 'user', {userId: 2}]` + * means that we want to generate a link to `/team;teamId=1/user;userId=2`. + * + * The first segment name can be prepended with `/`, `./`, or `../`. + * If the segment begins with `/`, the router will look up the route from the root of the app. + * If the segment begins with `./`, or doesn't begin with a slash, the router will + * instead look in the current component's children for the route. + * And if the segment begins with `../`, the router will go up one level. + */ +@Directive({selector: '[routerLink]'}) +export class RouterLink { + @Input() target: string; + private commands: any[]|null = null; + private absoluteUrl: string|null = null; + + // the url displayed on the anchor element. + @HostBinding() href: string; + + constructor(private router: Router) {} + + @Input() + set routerLink(data: any[] | string) { + if (Array.isArray(data)) { + this.commands = data; + this.absoluteUrl = null; + } else { + this.commands = null; + this.absoluteUrl = data; + } + 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.absoluteUrl); + return false; + } + return true; + } + + private updateTargetUrlAndHref(): void { + if (this.absoluteUrl) { + this.href = this.absoluteUrl; + } + } +} diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 856b40ac95..5a931d0056 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -66,6 +66,11 @@ export class Router { resetConfig(config: RouterConfig): void { this.config = config; } + + /** + * @internal + */ + dispose(): void { this.locationSubscription.unsubscribe(); } private setUpLocationChangeListener(): void { this.locationSubscription = this.location.subscribe((change) => { diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index 707f9a9564..eef2424212 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -153,20 +153,53 @@ describe("Integration", () => { expect(fixture.debugElement.nativeElement).toHaveText(''); }))); + + describe("router links", () => { + it("should support string router links", + fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => { + router.resetConfig([ + { name: 'team', path: 'team/:id', component: TeamCmp, children: [ + { name: 'link', path: 'link', component: StringLinkCmp }, + { name: 'simple', path: 'simple', component: SimpleCmp } + ] } + ]); + + const fixture = tcb.createFakeAsync(RootCmp); + advance(fixture); + + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 { link, right: }'); + + const native = fixture.debugElement.nativeElement.querySelector("a"); + expect(native.getAttribute("href")).toEqual("/team/33/simple"); + native.click(); + advance(fixture); + + expect(fixture.debugElement.nativeElement).toHaveText('team 33 { simple, right: }'); + }))); + }); }); +@Component({ + selector: 'link-cmp', + template: `link`, + directives: ROUTER_DIRECTIVES +}) +class StringLinkCmp {} @Component({ selector: 'simple-cmp', template: `simple`, - directives: [ROUTER_DIRECTIVES] + directives: ROUTER_DIRECTIVES }) -class SimpleCmp {} +class SimpleCmp { +} @Component({ selector: 'team-cmp', template: `team {{id | async}} { , right: }`, - directives: [ROUTER_DIRECTIVES] + directives: ROUTER_DIRECTIVES }) class TeamCmp { id: Observable;