diff --git a/modules/@angular/router/index.ts b/modules/@angular/router/index.ts index 1edfa14028..b07a00926a 100644 --- a/modules/@angular/router/index.ts +++ b/modules/@angular/router/index.ts @@ -1,7 +1,7 @@ /** * @module * @description - * Alternative implementation of the router. Experimental. + * Maps application URLs into application states, to support deep-linking and navigation. */ export {Router, RouterOutletMap} from './src/router'; @@ -15,4 +15,24 @@ export {ROUTER_PROVIDERS} from './src/router_providers'; import {RouterOutlet} from './src/directives/router_outlet'; import {RouterLink} from './src/directives/router_link'; +/** + * A list of directives. To use the router directives like {@link RouterOutlet} and + * {@link RouterLink}, add this to your `directives` array in the {@link View} decorator of your + * component. + * + * ``` + * import {Component} from '@angular/core'; + * import {ROUTER_DIRECTIVES, Routes} from '@angular/router-deprecated'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * // ... + * } + * + * bootstrap(AppCmp); + * ``` + */ export const ROUTER_DIRECTIVES: any[] = /*@ts2dart_const*/[RouterOutlet, RouterLink]; diff --git a/modules/@angular/router/src/constants.ts b/modules/@angular/router/src/constants.ts index 52ecd2e56e..0f0377abec 100644 --- a/modules/@angular/router/src/constants.ts +++ b/modules/@angular/router/src/constants.ts @@ -1 +1,5 @@ +/** + * Name of the default outlet outlet. + * @type {string} + */ export const DEFAULT_OUTLET_NAME = "__DEFAULT"; \ 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 index bb3d9e2343..3ed2da67e8 100644 --- a/modules/@angular/router/src/directives/router_link.ts +++ b/modules/@angular/router/src/directives/router_link.ts @@ -19,16 +19,49 @@ import {RouteSegment, UrlSegment, Tree} from '../segments'; import {isString, isArray, isPresent} from '../facade/lang'; import {ObservableWrapper} from '../facade/async'; +/** + * The RouterLink directive lets you link to specific parts of your app. + * + * Consider the following route configuration: + + * ``` + * @Routes([ + * { path: '/user', component: UserCmp } + * ]); + * class MyComp {} + * ``` + * + * 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 segment in the url. + * + * See {@link Router.createUrlTree} for more information. + */ @Directive({selector: '[routerLink]'}) export class RouterLink implements OnDestroy { @Input() target: string; private _commands: any[] = []; private _subscription: any; + // the url displayed on the anchor element. @HostBinding() href: string; @HostBinding('class.router-link-active') isActive: boolean = false; constructor(@Optional() private _routeSegment: RouteSegment, private _router: Router) { + // because auxiliary links take existing primary and auxiliary routes into account, + // we need to update the link whenever params or other routes change. this._subscription = ObservableWrapper.subscribe(_router.changes, (_) => { this._updateTargetUrlAndHref(); }); } @@ -48,6 +81,7 @@ export class RouterLink implements OnDestroy { @HostListener("click") onClick(): boolean { + // If no target, or if target is _self, prevent default browser behavior if (!isString(this.target) || this.target == '_self') { this._router.navigate(this._commands, this._routeSegment); return false; diff --git a/modules/@angular/router/src/directives/router_outlet.ts b/modules/@angular/router/src/directives/router_outlet.ts index 242d37facc..2ce7da5964 100644 --- a/modules/@angular/router/src/directives/router_outlet.ts +++ b/modules/@angular/router/src/directives/router_outlet.ts @@ -13,6 +13,21 @@ import {RouterOutletMap} from '../router'; import {DEFAULT_OUTLET_NAME} from '../constants'; import {isPresent, isBlank} from '../facade/lang'; +/** + * A router outlet is a placeholder that Angular dynamically fills based on the application's route. + * + * ## Use + * + * ``` + * + * ``` + * + * Outlets can be named. + * + * ``` + * + * ``` + */ @Directive({selector: 'router-outlet'}) export class RouterOutlet { private _loaded: ComponentRef; @@ -28,10 +43,19 @@ export class RouterOutlet { this._loaded = null; } + /** + * Returns the loaded component. + */ get loadedComponent(): Object { return isPresent(this._loaded) ? this._loaded.instance : null; } + /** + * Returns true is the outlet is not empty. + */ get isLoaded(): boolean { return isPresent(this._loaded); } + /** + * Called by the Router to instantiate a new component. + */ load(factory: ComponentFactory, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): ComponentRef { this.outletMap = outletMap; diff --git a/modules/@angular/router/src/interfaces.ts b/modules/@angular/router/src/interfaces.ts index c09b38df5f..54667579a2 100644 --- a/modules/@angular/router/src/interfaces.ts +++ b/modules/@angular/router/src/interfaces.ts @@ -1,10 +1,26 @@ import {RouteSegment, Tree, RouteTree} from './segments'; +/** + * Defines route lifecycle method `routerOnActivate`, which is called by the router at the end of a + * successful route navigation. + * + * The `routerOnActivate` hook is called with the current and previous {@link RouteSegment}s of the + * component and with the corresponding route trees. + */ export interface OnActivate { routerOnActivate(curr: RouteSegment, prev?: RouteSegment, currTree?: RouteTree, prevTree?: RouteTree): void; } +/** + * Defines route lifecycle method `routerOnDeactivate`, which is called by the router before + * destroying a component as part of a route change. + * + * The `routerOnDeactivate` hook is called with two {@link RouteTree}s, representing the current + * and the future state of the application. + * + * `routerOnDeactivate` must return a promise. The route change will wait until the promise settles. + */ export interface CanDeactivate { routerCanDeactivate(currTree?: RouteTree, futureTree?: RouteTree): Promise; } \ No newline at end of file diff --git a/modules/@angular/router/src/link.ts b/modules/@angular/router/src/link.ts index a2d05ae0c9..f79105bd4c 100644 --- a/modules/@angular/router/src/link.ts +++ b/modules/@angular/router/src/link.ts @@ -3,6 +3,7 @@ import {isBlank, isPresent, isString, isStringMap} from './facade/lang'; import {BaseException} from './facade/exceptions'; import {ListWrapper} from './facade/collection'; +// TODO: vsavkin: should reuse segments export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, commands: any[]): UrlTree { if (commands.length === 0) return urlTree; diff --git a/modules/@angular/router/src/metadata/decorators.dart b/modules/@angular/router/src/metadata/decorators.dart index b230a6c4c0..5f4dce731d 100644 --- a/modules/@angular/router/src/metadata/decorators.dart +++ b/modules/@angular/router/src/metadata/decorators.dart @@ -3,6 +3,11 @@ library angular.alt_router.decorators; import 'metadata.dart'; export 'metadata.dart'; +/** + * Defines routes for a given component. + * + * It takes an array of {@link RouteMetadata}s. + */ class Routes extends RoutesMetadata { const Routes(List routes): super(routes); } \ No newline at end of file diff --git a/modules/@angular/router/src/metadata/decorators.ts b/modules/@angular/router/src/metadata/decorators.ts index 63353c5b8b..581d7e6024 100644 --- a/modules/@angular/router/src/metadata/decorators.ts +++ b/modules/@angular/router/src/metadata/decorators.ts @@ -1,7 +1,19 @@ import {RoutesMetadata, RouteMetadata} from "./metadata"; import {makeDecorator} from '../core_private'; + +/** + * Defines routes for a given component. + * + * It takes an array of {@link RouteMetadata}s. + */ export interface RoutesFactory { (routes: RouteMetadata[]): any; new (routes: RouteMetadata[]): RoutesMetadata; } + +/** + * Defines routes for a given component. + * + * It takes an array of {@link RouteMetadata}s. + */ export var Routes: RoutesFactory = makeDecorator(RoutesMetadata); diff --git a/modules/@angular/router/src/metadata/metadata.ts b/modules/@angular/router/src/metadata/metadata.ts index 18d3f64c66..fc6263c648 100644 --- a/modules/@angular/router/src/metadata/metadata.ts +++ b/modules/@angular/router/src/metadata/metadata.ts @@ -1,12 +1,34 @@ import {Type} from '@angular/core'; import {stringify} from "../facade/lang"; +/** + * Information about a route. + * + * It has the following properties: + * - `path` is a string that uses the route matcher DSL. + * - `component` a component type. + * + * ### Example + * ``` + * import {Routes} from '@angular/router'; + * + * @Routes([ + * {path: '/home', component: HomeCmp} + * ]) + * class MyApp {} + * ``` + * + * @ts2dart_const + */ export abstract class RouteMetadata { abstract get path(): string; abstract get component(): Type; } -/* @ts2dart_const */ +/** + * See {@link RouteMetadata} for more information. + * @ts2dart_const + */ export class Route implements RouteMetadata { path: string; component: Type; @@ -17,7 +39,12 @@ export class Route implements RouteMetadata { toString(): string { return `@Route(${this.path}, ${stringify(this.component)})`; } } -/* @ts2dart_const */ +/** + * Defines routes for a given component. + * + * It takes an array of {@link RouteMetadata}s. + * @ts2dart_const + */ export class RoutesMetadata { constructor(public routes: RouteMetadata[]) {} toString(): string { return `@Routes(${this.routes})`; } diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index e729aaecc3..6e51b7e7fa 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -25,18 +25,30 @@ import { import {hasLifecycleHook} from './lifecycle_reflector'; import {DEFAULT_OUTLET_NAME} from './constants'; +/** + * @internal + */ export class RouterOutletMap { /** @internal */ _outlets: {[name: string]: RouterOutlet} = {}; registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; } } +/** + * The `Router` is responsible for mapping URLs to components. + * + * You can see the state of the router by inspecting the read-only fields `router.urlTree` + * and `router.routeTree`. + */ export class Router { private _prevTree: RouteTree; private _urlTree: UrlTree; private _locationSubscription: any; private _changes: EventEmitter = new EventEmitter(); + /** + * @internal + */ constructor(private _rootComponent: Object, private _rootComponentType: Type, private _componentResolver: ComponentResolver, private _urlSerializer: RouterUrlSerializer, @@ -46,18 +58,94 @@ export class Router { this.navigateByUrl(this._location.path()); } + /** + * Returns the current url tree. + */ get urlTree(): UrlTree { return this._urlTree; } + /** + * Returns the current route tree. + */ + get routeTree(): RouteTree { return this._prevTree; } + + /** + * An observable or url changes from the router. + */ + get changes(): Observable { return this._changes; } + + /** + * Navigate based on the provided url. This navigation is always absolute. + * + * ### Usage + * + * ``` + * router.navigateByUrl("/team/33/user/11"); + * ``` + */ navigateByUrl(url: string): Promise { return this._navigate(this._urlSerializer.parse(url)); } + /** + * Navigate based on the provided array of commands and a starting point. + * If no segment is provided, the navigation is absolute. + * + * ### Usage + * + * ``` + * router.navigate(['team', 33, 'team', '11], segment); + * ``` + */ navigate(commands: any[], segment?: RouteSegment): Promise { return this._navigate(this.createUrlTree(commands, segment)); } + /** + * @internal + */ dispose(): void { ObservableWrapper.dispose(this._locationSubscription); } + /** + * Applies an array of commands to the current url tree and creates + * a new url tree. + * + * When given a segment, applies the given commands starting from the segment. + * When not given a segment, applies the given command starting from the root. + * + * ### Usage + * + * ``` + * // create /team/33/user/11 + * router.createUrlTree(['/team', 33, 'user', 11]); + * + * // create /team/33;expand=true/user/11 + * router.createUrlTree(['/team', 33, {expand: true}, 'user', 11]); + * + * // you can collapse static fragments like this + * router.createUrlTree(['/team/33/user', userId]); + * + * // assuming the current url is `/team/33/user/11` and the segment points to `user/11` + * + * // navigate to /team/33/user/11/details + * router.createUrlTree(['details'], segment); + * + * // navigate to /team/33/user/22 + * router.createUrlTree(['../22'], segment); + * + * // navigate to /team/44/user/22 + * router.createUrlTree(['../../team/44/user/22'], segment); + * ``` + */ + createUrlTree(commands: any[], segment?: RouteSegment): UrlTree { + let s = isPresent(segment) ? segment : this._prevTree.root; + return link(s, this._prevTree, this.urlTree, commands); + } + + /** + * Serializes a {@link UrlTree} into a string. + */ + serializeUrl(url: UrlTree): string { return this._urlSerializer.serialize(url); } + private _createInitialTree(): RouteTree { let root = new RouteSegment([new UrlSegment("", {}, null)], {}, DEFAULT_OUTLET_NAME, this._rootComponentType, null); @@ -84,17 +172,6 @@ export class Router { }); }); } - - createUrlTree(commands: any[], segment?: RouteSegment): UrlTree { - let s = isPresent(segment) ? segment : this._prevTree.root; - return link(s, this._prevTree, this.urlTree, commands); - } - - serializeUrl(url: UrlTree): string { return this._urlSerializer.serialize(url); } - - get changes(): Observable { return this._changes; } - - get routeTree(): RouteTree { return this._prevTree; } } diff --git a/modules/@angular/router/src/router_providers.ts b/modules/@angular/router/src/router_providers.ts index 1dfb6a3c7b..3bc15761ab 100644 --- a/modules/@angular/router/src/router_providers.ts +++ b/modules/@angular/router/src/router_providers.ts @@ -2,6 +2,28 @@ import {ROUTER_PROVIDERS_COMMON} from './router_providers_common'; import {BrowserPlatformLocation} from '@angular/platform-browser'; import {PlatformLocation} from '@angular/common'; +/** + * A list of {@link Provider}s. To use the router, you must add this to your application. + * + * ``` + * import {Component} from '@angular/core'; + * import { + * ROUTER_DIRECTIVES, + * ROUTER_PROVIDERS, + * Routes + * } from '@angular/router'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @Routes([ + * {...}, + * ]) + * class AppCmp { + * // ... + * } + * + * bootstrap(AppCmp, [ROUTER_PROVIDERS]); + * ``` + */ export const ROUTER_PROVIDERS: any[] = /*@ts2dart_const*/[ ROUTER_PROVIDERS_COMMON, /*@ts2dart_Provider*/ {provide: PlatformLocation, useClass: BrowserPlatformLocation}, diff --git a/modules/@angular/router/src/router_providers_common.ts b/modules/@angular/router/src/router_providers_common.ts index b3822ef38c..429201f4f7 100644 --- a/modules/@angular/router/src/router_providers_common.ts +++ b/modules/@angular/router/src/router_providers_common.ts @@ -5,6 +5,9 @@ import {RouterUrlSerializer, DefaultRouterUrlSerializer} from './router_url_seri import {ApplicationRef} from '@angular/core'; import {BaseException} from '@angular/core'; +/** + * The Platform agnostic ROUTER PROVIDERS + */ export const ROUTER_PROVIDERS_COMMON: any[] = /*@ts2dart_const*/[ RouterOutletMap, /*@ts2dart_Provider*/ {provide: RouterUrlSerializer, useClass: DefaultRouterUrlSerializer}, diff --git a/modules/@angular/router/src/router_url_serializer.ts b/modules/@angular/router/src/router_url_serializer.ts index ada4462f3d..1d05564a95 100644 --- a/modules/@angular/router/src/router_url_serializer.ts +++ b/modules/@angular/router/src/router_url_serializer.ts @@ -2,11 +2,24 @@ import {UrlSegment, Tree, TreeNode, rootNode, UrlTree} from './segments'; import {BaseException} from '@angular/core'; import {isBlank, isPresent, RegExpWrapper} from './facade/lang'; +/** + * Defines a way to serialize/deserialize a url tree. + */ export abstract class RouterUrlSerializer { + /** + * Parse a url into a {@Link UrlTree} + */ abstract parse(url: string): UrlTree; + + /** + * Converts a {@Link UrlTree} into a url + */ abstract serialize(tree: UrlTree): string; } +/** + * A default implementation of the serialization. + */ export class DefaultRouterUrlSerializer extends RouterUrlSerializer { parse(url: string): UrlTree { let root = new _UrlParser().parse(url);