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;