cleanup(router): removes router

This commit is contained in:
vsavkin
2016-06-20 08:09:16 -07:00
parent 65be81baf8
commit af2f5c3d7d
65 changed files with 19 additions and 5172 deletions

View File

@ -1,5 +0,0 @@
/**
* Name of the default outlet outlet.
* @type {string}
*/
export const DEFAULT_OUTLET_NAME = '__DEFAULT';

View File

@ -1,4 +0,0 @@
import {__core_private__ as _} from '@angular/core';
export var makeDecorator: typeof _.makeDecorator = _.makeDecorator;
export var reflector: typeof _.reflector = _.reflector;

View File

@ -1,25 +0,0 @@
import {RouterLink} from './router_link';
import {RouterOutlet} from './router_outlet';
/**
* 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';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @Routes([
* {...},
* ])
* class AppCmp {
* // ...
* }
*
* bootstrap(AppCmp);
* ```
*/
export const ROUTER_DIRECTIVES: any[] = /*@ts2dart_const*/[RouterOutlet, RouterLink];

View File

@ -1,95 +0,0 @@
import {LocationStrategy} from '@angular/common';
import {Directive, HostBinding, HostListener, Input, OnDestroy} from '@angular/core';
import {ObservableWrapper} from '../facade/async';
import {isArray, isPresent, isString} from '../facade/lang';
import {Router} from '../router';
import {RouteSegment} from '../segments';
/**
* 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:
*
* ```
* <a [routerLink]="['/user']">link to user component</a>
* ```
*
* 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(
private _routeSegment: RouteSegment, private _router: Router,
private _locationStrategy: LocationStrategy) {
// 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(); });
}
ngOnDestroy() { ObservableWrapper.dispose(this._subscription); }
@Input()
set routerLink(data: any[]|any) {
if (isArray(data)) {
this._commands = <any[]>data;
} else {
this._commands = [data];
}
this._updateTargetUrlAndHref();
}
@HostListener('click', ['$event.button', '$event.ctrlKey', '$event.metaKey'])
onClick(button: number, ctrlKey: boolean, metaKey: boolean): boolean {
if (button != 0 || ctrlKey || metaKey) {
return true;
}
if (isString(this.target) && this.target != '_self') {
return true;
}
this._router.navigate(this._commands, this._routeSegment);
return false;
}
private _updateTargetUrlAndHref(): void {
let tree = this._router.createUrlTree(this._commands, this._routeSegment);
if (isPresent(tree)) {
this.href = this._locationStrategy.prepareExternalUrl(this._router.serializeUrl(tree));
this.isActive = this._router.urlTree.contains(tree);
} else {
this.isActive = false;
}
}
}

View File

@ -1,60 +0,0 @@
import {Attribute, ComponentFactory, ComponentRef, Directive, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core';
import {DEFAULT_OUTLET_NAME} from '../constants';
import {isBlank, isPresent} from '../facade/lang';
import {RouterOutletMap} from '../router';
/**
* A router outlet is a placeholder that Angular dynamically fills based on the application's route.
*
* ## Use
*
* ```
* <router-outlet></router-outlet>
* ```
*
* Outlets can be named.
*
* ```
* <router-outlet name="right"></router-outlet>
* ```
*/
@Directive({selector: 'router-outlet'})
export class RouterOutlet {
private _activated: ComponentRef<any>;
public outletMap: RouterOutletMap;
constructor(
parentOutletMap: RouterOutletMap, private _location: ViewContainerRef,
@Attribute('name') name: string) {
parentOutletMap.registerOutlet(isBlank(name) ? DEFAULT_OUTLET_NAME : name, this);
}
deactivate(): void {
this._activated.destroy();
this._activated = null;
}
/**
* Returns the loaded component.
*/
get component(): Object { return isPresent(this._activated) ? this._activated.instance : null; }
/**
* Returns true is the outlet is not empty.
*/
get isActivated(): boolean { return isPresent(this._activated); }
/**
* Called by the Router to instantiate a new component.
*/
activate(
factory: ComponentFactory<any>, providers: ResolvedReflectiveProvider[],
outletMap: RouterOutletMap): ComponentRef<any> {
this.outletMap = outletMap;
let inj = ReflectiveInjector.fromResolvedProviders(providers, this._location.parentInjector);
this._activated = this._location.createComponent(factory, this._location.length, inj, []);
return this._activated;
}
}

View File

@ -1 +0,0 @@
../../facade/src

View File

@ -1,27 +0,0 @@
import {RouteSegment, RouteTree, Tree} 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<boolean>;
}

View File

@ -1,6 +0,0 @@
import './interfaces.dart';
bool hasLifecycleHook(String name, Object obj) {
if (name == "routerOnActivate") return obj is OnActivate;
if (name == "routerCanDeactivate") return obj is CanDeactivate;
return false;
}

View File

@ -1,8 +0,0 @@
import {Type, isBlank} from './facade/lang';
export function hasLifecycleHook(name: string, obj: Object): boolean {
if (isBlank(obj)) return false;
let type = obj.constructor;
if (!(type instanceof Type)) return false;
return name in (<any>type).prototype;
}

View File

@ -1,213 +0,0 @@
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {BaseException} from './facade/exceptions';
import {isBlank, isPresent, isString, isStringMap} from './facade/lang';
import {RouteSegment, RouteTree, Tree, TreeNode, UrlSegment, UrlTree, rootNode} from './segments';
export function link(
segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, commands: any[]): UrlTree {
if (commands.length === 0) return urlTree;
let normalizedCommands = _normalizeCommands(commands);
if (_navigateToRoot(normalizedCommands)) {
return new UrlTree(new TreeNode<UrlSegment>(urlTree.root, []));
}
let startingNode = _findStartingNode(normalizedCommands, urlTree, segment, routeTree);
let updated = normalizedCommands.commands.length > 0 ?
_updateMany(ListWrapper.clone(startingNode.children), normalizedCommands.commands) :
[];
let newRoot = _constructNewTree(rootNode(urlTree), startingNode, updated);
return new UrlTree(newRoot);
}
function _navigateToRoot(normalizedChange: _NormalizedNavigationCommands): boolean {
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 &&
normalizedChange.commands[0] == '/';
}
class _NormalizedNavigationCommands {
constructor(
public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {}
}
function _normalizeCommands(commands: any[]): _NormalizedNavigationCommands {
if (isString(commands[0]) && commands.length === 1 && commands[0] == '/') {
return new _NormalizedNavigationCommands(true, 0, commands);
}
let numberOfDoubleDots = 0;
let isAbsolute = false;
let res: any[] /** TODO #9100 */ = [];
for (let i = 0; i < commands.length; ++i) {
let c = commands[i];
if (!isString(c)) {
res.push(c);
continue;
}
let parts = c.split('/');
for (let j = 0; j < parts.length; ++j) {
let cc = parts[j];
// first exp is treated in a special way
if (i == 0) {
if (j == 0 && cc == '.') { // './a'
// skip it
} else if (j == 0 && cc == '') { // '/a'
isAbsolute = true;
} else if (cc == '..') { // '../a'
numberOfDoubleDots++;
} else if (cc != '') {
res.push(cc);
}
} else {
if (cc != '') {
res.push(cc);
}
}
}
}
return new _NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
}
function _findUrlSegment(
segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree,
numberOfDoubleDots: number): UrlSegment {
let s = segment;
while (s.urlSegments.length === 0) {
s = routeTree.parent(s);
}
let urlSegment = ListWrapper.last(s.urlSegments);
let path = urlTree.pathFromRoot(urlSegment);
if (path.length <= numberOfDoubleDots) {
throw new BaseException('Invalid number of \'../\'');
}
return path[path.length - 1 - numberOfDoubleDots];
}
function _findStartingNode(
normalizedChange: _NormalizedNavigationCommands, urlTree: UrlTree, segment: RouteSegment,
routeTree: RouteTree): TreeNode<UrlSegment> {
if (normalizedChange.isAbsolute) {
return rootNode(urlTree);
} else {
let urlSegment =
_findUrlSegment(segment, routeTree, urlTree, normalizedChange.numberOfDoubleDots);
return _findMatchingNode(urlSegment, rootNode(urlTree));
}
}
function _findMatchingNode(segment: UrlSegment, node: TreeNode<UrlSegment>): TreeNode<UrlSegment> {
if (node.value === segment) return node;
for (var c of node.children) {
let r = _findMatchingNode(segment, c);
if (isPresent(r)) return r;
}
return null;
}
function _constructNewTree(
node: TreeNode<UrlSegment>, original: TreeNode<UrlSegment>,
updated: TreeNode<UrlSegment>[]): TreeNode<UrlSegment> {
if (node === original) {
return new TreeNode<UrlSegment>(node.value, updated);
} else {
return new TreeNode<UrlSegment>(
node.value, node.children.map(c => _constructNewTree(c, original, updated)));
}
}
function _update(node: TreeNode<UrlSegment>, commands: any[]): TreeNode<UrlSegment> {
let rest = commands.slice(1);
let next = rest.length === 0 ? null : rest[0];
let outlet = _outlet(commands);
let segment = _segment(commands);
// reach the end of the tree => create new tree nodes.
if (isBlank(node) && !isStringMap(next)) {
let urlSegment = new UrlSegment(segment, {}, outlet);
let children = rest.length === 0 ? [] : [_update(null, rest)];
return new TreeNode<UrlSegment>(urlSegment, children);
} else if (isBlank(node) && isStringMap(next)) {
let urlSegment = new UrlSegment(segment, _stringify(next), outlet);
return _recurse(urlSegment, node, rest.slice(1));
// different outlet => preserve the subtree
} else if (outlet != node.value.outlet) {
return node;
// params command
} else if (isStringMap(segment)) {
let newSegment = new UrlSegment(node.value.segment, _stringify(segment), node.value.outlet);
return _recurse(newSegment, node, rest);
// next one is a params command && can reuse the node
} else if (isStringMap(next) && _compare(segment, _stringify(next), node.value)) {
return _recurse(node.value, node, rest.slice(1));
// next one is a params command && cannot reuse the node
} else if (isStringMap(next)) {
let urlSegment = new UrlSegment(segment, _stringify(next), outlet);
return _recurse(urlSegment, node, rest.slice(1));
// next one is not a params command && can reuse the node
} else if (_compare(segment, {}, node.value)) {
return _recurse(node.value, node, rest);
// next one is not a params command && cannot reuse the node
} else {
let urlSegment = new UrlSegment(segment, {}, outlet);
return _recurse(urlSegment, node, rest);
}
}
function _stringify(params: {[key: string]: any}): {[key: string]: string} {
let res = {};
StringMapWrapper.forEach(
params, (v: any /** TODO #9100 */, k: any /** TODO #9100 */) =>
(res as any /** TODO #9100 */)[k] = v.toString());
return res;
}
function _compare(path: string, params: {[key: string]: any}, segment: UrlSegment): boolean {
return path == segment.segment && StringMapWrapper.equals(params, segment.parameters);
}
function _recurse(
urlSegment: UrlSegment, node: TreeNode<UrlSegment>, rest: any[]): TreeNode<UrlSegment> {
if (rest.length === 0) {
return new TreeNode<UrlSegment>(urlSegment, []);
}
return new TreeNode<UrlSegment>(urlSegment, _updateMany(ListWrapper.clone(node.children), rest));
}
function _updateMany(nodes: TreeNode<UrlSegment>[], commands: any[]): TreeNode<UrlSegment>[] {
let outlet = _outlet(commands);
let nodesInRightOutlet = nodes.filter(c => c.value.outlet == outlet);
if (nodesInRightOutlet.length > 0) {
let nodeRightOutlet = nodesInRightOutlet[0]; // there can be only one
nodes[nodes.indexOf(nodeRightOutlet)] = _update(nodeRightOutlet, commands);
} else {
nodes.push(_update(null, commands));
}
return nodes;
}
function _segment(commands: any[]): any {
if (!isString(commands[0])) return commands[0];
let parts = commands[0].toString().split(':');
return parts.length > 1 ? parts[1] : commands[0];
}
function _outlet(commands: any[]): string {
if (!isString(commands[0])) return null;
let parts = commands[0].toString().split(':');
return parts.length > 1 ? parts[0] : null;
}

View File

@ -1,13 +0,0 @@
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<RouteMetadata> routes): super(routes);
}

View File

@ -1,22 +0,0 @@
import {makeDecorator} from '../core_private';
import {RouteMetadata, RoutesMetadata} from './metadata';
/**
* 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.
* @Annotation
*/
export var Routes: RoutesFactory = <RoutesFactory>makeDecorator(RoutesMetadata);

View File

@ -1,51 +0,0 @@
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|string;
}
/**
* See {@link RouteMetadata} for more information.
* @ts2dart_const
*/
export class Route implements RouteMetadata {
path: string;
component: Type|string;
constructor({path, component}: {path?: string, component?: Type|string} = {}) {
this.path = path;
this.component = component;
}
toString(): string { return `@Route(${this.path}, ${stringify(this.component)})`; }
}
/**
* 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})`; }
}

View File

@ -1,210 +0,0 @@
import {BaseException, ComponentFactory, ComponentResolver} from '@angular/core';
import {DEFAULT_OUTLET_NAME} from './constants';
import {reflector} from './core_private';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {Type, isBlank, isPresent, stringify} from './facade/lang';
import {PromiseWrapper} from './facade/promise';
import {RouteMetadata, RoutesMetadata} from './metadata/metadata';
import {RouteSegment, RouteTree, Tree, TreeNode, UrlSegment, UrlTree, equalUrlSegments, rootNode} from './segments';
export function recognize(
componentResolver: ComponentResolver, rootComponent: Type, url: UrlTree,
existingTree: RouteTree): Promise<RouteTree> {
let matched = new _MatchResult(rootComponent, [url.root], {}, rootNode(url).children, []);
return _constructSegment(componentResolver, matched, rootNode(existingTree))
.then(roots => new RouteTree(roots[0]));
}
function _recognize(
componentResolver: ComponentResolver, parentComponent: Type, url: TreeNode<UrlSegment>,
existingSegments: TreeNode<RouteSegment>[]): Promise<TreeNode<RouteSegment>[]> {
let metadata = _readMetadata(parentComponent); // should read from the factory instead
if (isBlank(metadata)) {
throw new BaseException(
`Component '${stringify(parentComponent)}' does not have route configuration`);
}
let match: any /** TODO #9100 */;
try {
match = _match(metadata, url);
} catch (e) {
return PromiseWrapper.reject(e, null);
}
let segmentsWithRightOutlet = existingSegments.filter(r => r.value.outlet == match.outlet);
let segmentWithRightOutlet =
segmentsWithRightOutlet.length > 0 ? segmentsWithRightOutlet[0] : null;
let main = _constructSegment(componentResolver, match, segmentWithRightOutlet);
let aux = _recognizeMany(componentResolver, parentComponent, match.aux, existingSegments)
.then(_checkOutletNameUniqueness);
return PromiseWrapper.all([main, aux]).then(ListWrapper.flatten);
}
function _recognizeMany(
componentResolver: ComponentResolver, parentComponent: Type, urls: TreeNode<UrlSegment>[],
existingSegments: TreeNode<RouteSegment>[]): Promise<TreeNode<RouteSegment>[]> {
let recognized =
urls.map(u => _recognize(componentResolver, parentComponent, u, existingSegments));
return PromiseWrapper.all(recognized).then(ListWrapper.flatten);
}
function _constructSegment(
componentResolver: ComponentResolver, matched: _MatchResult,
existingSegment: TreeNode<RouteSegment>): Promise<TreeNode<RouteSegment>[]> {
return componentResolver.resolveComponent(matched.component).then(factory => {
let segment = _createOrReuseSegment(matched, factory, existingSegment);
let existingChildren = isPresent(existingSegment) ? existingSegment.children : [];
if (matched.leftOverUrl.length > 0) {
return _recognizeMany(
componentResolver, factory.componentType, matched.leftOverUrl, existingChildren)
.then(children => [new TreeNode<RouteSegment>(segment, children)]);
} else {
return _recognizeLeftOvers(componentResolver, factory.componentType, existingChildren)
.then(children => [new TreeNode<RouteSegment>(segment, children)]);
}
});
}
function _createOrReuseSegment(
matched: _MatchResult, factory: ComponentFactory<any>,
segmentNode: TreeNode<RouteSegment>): RouteSegment {
let segment = isPresent(segmentNode) ? segmentNode.value : null;
if (isPresent(segment) && equalUrlSegments(segment.urlSegments, matched.consumedUrlSegments) &&
StringMapWrapper.equals(segment.parameters, matched.parameters) &&
segment.outlet == matched.outlet && factory.componentType == segment.type) {
return segment;
} else {
return new RouteSegment(
matched.consumedUrlSegments, matched.parameters, matched.outlet, factory.componentType,
factory);
}
}
function _recognizeLeftOvers(
componentResolver: ComponentResolver, parentComponent: Type,
existingSegments: TreeNode<RouteSegment>[]): Promise<TreeNode<RouteSegment>[]> {
return componentResolver.resolveComponent(parentComponent).then(factory => {
let metadata = _readMetadata(factory.componentType);
if (isBlank(metadata)) {
return [];
}
let r = (<any[]>metadata.routes).filter(r => r.path == '' || r.path == '/');
if (r.length === 0) {
return PromiseWrapper.resolve([]);
} else {
let segmentsWithMatchingOutlet =
existingSegments.filter(r => r.value.outlet == DEFAULT_OUTLET_NAME);
let segmentWithMatchingOutlet =
segmentsWithMatchingOutlet.length > 0 ? segmentsWithMatchingOutlet[0] : null;
let existingChildren =
isPresent(segmentWithMatchingOutlet) ? segmentWithMatchingOutlet.children : [];
return _recognizeLeftOvers(componentResolver, r[0].component, existingChildren)
.then(children => {
return componentResolver.resolveComponent(r[0].component).then(factory => {
let segment = _createOrReuseSegment(
new _MatchResult(r[0].component, [], {}, [], []), factory,
segmentWithMatchingOutlet);
return [new TreeNode<RouteSegment>(segment, children)];
});
});
}
});
}
function _match(metadata: RoutesMetadata, url: TreeNode<UrlSegment>): _MatchResult {
for (let r of metadata.routes) {
let matchingResult = _matchWithParts(r, url);
if (isPresent(matchingResult)) {
return matchingResult;
}
}
let availableRoutes = metadata.routes.map(r => `'${r.path}'`).join(', ');
throw new BaseException(
`Cannot match any routes. Current segment: '${url.value}'. Available routes: [${availableRoutes}].`);
}
function _matchWithParts(route: RouteMetadata, url: TreeNode<UrlSegment>): _MatchResult {
let path = route.path.startsWith('/') ? route.path.substring(1) : route.path;
if (path == '*') {
return new _MatchResult(route.component, [], null, [], []);
}
let parts = path.split('/');
let positionalParams = {};
let consumedUrlSegments: any[] /** TODO #9100 */ = [];
let lastParent: TreeNode<UrlSegment> = null;
let lastSegment: TreeNode<UrlSegment> = null;
let current = url;
for (let i = 0; i < parts.length; ++i) {
if (isBlank(current)) return null;
let p = parts[i];
let isLastSegment = i === parts.length - 1;
let isLastParent = i === parts.length - 2;
let isPosParam = p.startsWith(':');
if (!isPosParam && p != current.value.segment) return null;
if (isLastSegment) {
lastSegment = current;
}
if (isLastParent) {
lastParent = current;
}
if (isPosParam) {
(positionalParams as any /** TODO #9100 */)[p.substring(1)] = current.value.segment;
}
consumedUrlSegments.push(current.value);
current = ListWrapper.first(current.children);
}
let p = lastSegment.value.parameters;
let parameters = <{[key: string]: string}>StringMapWrapper.merge(p, positionalParams);
let axuUrlSubtrees = isPresent(lastParent) ? lastParent.children.slice(1) : [];
return new _MatchResult(
route.component, consumedUrlSegments, parameters, lastSegment.children, axuUrlSubtrees);
}
function _checkOutletNameUniqueness(nodes: TreeNode<RouteSegment>[]): TreeNode<RouteSegment>[] {
let names = {};
nodes.forEach(n => {
let segmentWithSameOutletName = (names as any /** TODO #9100 */)[n.value.outlet];
if (isPresent(segmentWithSameOutletName)) {
let p = segmentWithSameOutletName.stringifiedUrlSegments;
let c = n.value.stringifiedUrlSegments;
throw new BaseException(`Two segments cannot have the same outlet name: '${p}' and '${c}'.`);
}
(names as any /** TODO #9100 */)[n.value.outlet] = n.value;
});
return nodes;
}
class _MatchResult {
constructor(
public component: Type|string, public consumedUrlSegments: UrlSegment[],
public parameters: {[key: string]: string}, public leftOverUrl: TreeNode<UrlSegment>[],
public aux: TreeNode<UrlSegment>[]) {}
get outlet(): string {
return this.consumedUrlSegments.length === 0 || isBlank(this.consumedUrlSegments[0].outlet) ?
DEFAULT_OUTLET_NAME :
this.consumedUrlSegments[0].outlet;
}
}
function _readMetadata(componentType: Type) {
let metadata = reflector.annotations(componentType).filter(f => f instanceof RoutesMetadata);
return ListWrapper.first(metadata);
}

View File

@ -1,282 +0,0 @@
import {Location} from '@angular/common';
import {BaseException, ComponentResolver, ReflectiveInjector, provide} from '@angular/core';
import {DEFAULT_OUTLET_NAME} from './constants';
import {RouterOutlet} from './directives/router_outlet';
import {EventEmitter, Observable, ObservableWrapper, PromiseWrapper} from './facade/async';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {Type, isBlank, isPresent} from './facade/lang';
import {CanDeactivate} from './interfaces';
import {hasLifecycleHook} from './lifecycle_reflector';
import {link} from './link';
import {recognize} from './recognize';
import {RouterUrlSerializer} from './router_url_serializer';
import {RouteSegment, RouteTree, TreeNode, UrlSegment, UrlTree, createEmptyRouteTree, rootNode, routeSegmentComponentFactory, serializeRouteSegmentTree} from './segments';
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 _routeTree: RouteTree;
private _urlTree: UrlTree;
private _locationSubscription: any;
private _changes: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
*/
constructor(
private _rootComponent: Object, private _rootComponentType: Type,
private _componentResolver: ComponentResolver, private _urlSerializer: RouterUrlSerializer,
private _routerOutletMap: RouterOutletMap, private _location: Location) {
this._routeTree = createEmptyRouteTree(this._rootComponentType);
this._setUpLocationChangeListener();
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._routeTree; }
/**
* An observable or url changes from the router.
*/
get changes(): Observable<void> { 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<void> {
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<void> {
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._routeTree.root;
return link(s, this._routeTree, this.urlTree, commands);
}
/**
* Serializes a {@link UrlTree} into a string.
*/
serializeUrl(url: UrlTree): string { return this._urlSerializer.serialize(url); }
private _setUpLocationChangeListener(): void {
this._locationSubscription = this._location.subscribe(
(change) => { this._navigate(this._urlSerializer.parse(change['url']), change['pop']); });
}
private _navigate(url: UrlTree, preventPushState?: boolean): Promise<void> {
this._urlTree = url;
return recognize(this._componentResolver, this._rootComponentType, url, this._routeTree)
.then(currTree => {
return new _ActivateSegments(currTree, this._routeTree)
.activate(this._routerOutletMap, this._rootComponent)
.then(updated => {
if (updated) {
this._routeTree = currTree;
if (isBlank(preventPushState) || !preventPushState) {
let path = this._urlSerializer.serialize(this._urlTree);
if (this._location.isCurrentPathEqualTo(path)) {
this._location.replaceState(path);
} else {
this._location.go(path);
}
}
this._changes.emit(null);
}
});
});
}
}
class _ActivateSegments {
private deactivations: Object[][] = [];
private performMutation: boolean = true;
constructor(private currTree: RouteTree, private prevTree: RouteTree) {}
activate(parentOutletMap: RouterOutletMap, rootComponent: Object): Promise<boolean> {
let prevRoot = isPresent(this.prevTree) ? rootNode(this.prevTree) : null;
let currRoot = rootNode(this.currTree);
return this.canDeactivate(currRoot, prevRoot, parentOutletMap, rootComponent).then(res => {
this.performMutation = true;
if (res) {
this.activateChildSegments(currRoot, prevRoot, parentOutletMap, [rootComponent]);
}
return res;
});
}
private canDeactivate(
currRoot: TreeNode<RouteSegment>, prevRoot: TreeNode<RouteSegment>,
outletMap: RouterOutletMap, rootComponent: Object): Promise<boolean> {
this.performMutation = false;
this.activateChildSegments(currRoot, prevRoot, outletMap, [rootComponent]);
let allPaths = PromiseWrapper.all(this.deactivations.map(r => this.checkCanDeactivatePath(r)));
return allPaths.then((values: boolean[]) => values.filter(v => v).length === values.length);
}
private checkCanDeactivatePath(path: Object[]): Promise<boolean> {
let curr = PromiseWrapper.resolve(true);
for (let p of ListWrapper.reversed(path)) {
curr = curr.then(_ => {
if (hasLifecycleHook('routerCanDeactivate', p)) {
return (<CanDeactivate>p).routerCanDeactivate(this.prevTree, this.currTree);
} else {
return _;
}
});
}
return curr;
}
private activateChildSegments(
currNode: TreeNode<RouteSegment>, prevNode: TreeNode<RouteSegment>,
outletMap: RouterOutletMap, components: Object[]): void {
let prevChildren = isPresent(prevNode) ? prevNode.children.reduce((m, c) => {
(m as any /** TODO #9100 */)[c.value.outlet] = c;
return m;
}, {}) : {};
currNode.children.forEach(c => {
this.activateSegments(
c, (prevChildren as any /** TODO #9100 */)[c.value.outlet], outletMap, components);
StringMapWrapper.delete(prevChildren, c.value.outlet);
});
StringMapWrapper.forEach(
prevChildren, (v: any /** TODO #9100 */, k: any /** TODO #9100 */) =>
this.deactivateOutlet(outletMap._outlets[k], components));
}
activateSegments(
currNode: TreeNode<RouteSegment>, prevNode: TreeNode<RouteSegment>,
parentOutletMap: RouterOutletMap, components: Object[]): void {
let curr = currNode.value;
let prev = isPresent(prevNode) ? prevNode.value : null;
let outlet = this.getOutlet(parentOutletMap, currNode.value);
if (curr === prev) {
this.activateChildSegments(
currNode, prevNode, outlet.outletMap, components.concat([outlet.component]));
} else {
this.deactivateOutlet(outlet, components);
if (this.performMutation) {
let outletMap = new RouterOutletMap();
let component = this.activateNewSegments(outletMap, curr, prev, outlet);
this.activateChildSegments(currNode, prevNode, outletMap, components.concat([component]));
}
}
}
private activateNewSegments(
outletMap: RouterOutletMap, curr: RouteSegment, prev: RouteSegment,
outlet: RouterOutlet): Object {
let resolved = ReflectiveInjector.resolve(
[{provide: RouterOutletMap, useValue: outletMap}, {provide: RouteSegment, useValue: curr}]);
let ref = outlet.activate(routeSegmentComponentFactory(curr), resolved, outletMap);
if (hasLifecycleHook('routerOnActivate', ref.instance)) {
ref.instance.routerOnActivate(curr, prev, this.currTree, this.prevTree);
}
return ref.instance;
}
private getOutlet(outletMap: RouterOutletMap, segment: RouteSegment): RouterOutlet {
let outlet = outletMap._outlets[segment.outlet];
if (isBlank(outlet)) {
if (segment.outlet == DEFAULT_OUTLET_NAME) {
throw new BaseException(`Cannot find default outlet`);
} else {
throw new BaseException(`Cannot find the outlet ${segment.outlet}`);
}
}
return outlet;
}
private deactivateOutlet(outlet: RouterOutlet, components: Object[]): void {
if (isPresent(outlet) && outlet.isActivated) {
StringMapWrapper.forEach(
outlet.outletMap._outlets, (v: any /** TODO #9100 */, k: any /** TODO #9100 */) =>
this.deactivateOutlet(v, components));
if (this.performMutation) {
outlet.deactivate();
} else {
this.deactivations.push(components.concat([outlet.component]));
}
}
}
}

View File

@ -1,26 +0,0 @@
import {ROUTER_PROVIDERS_COMMON} from './router_providers_common';
/**
* A list of providers. 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]);
* ```
*/
// TODO: merge with router_providers_common.ts
export const ROUTER_PROVIDERS: any[] = /*@ts2dart_const*/[ROUTER_PROVIDERS_COMMON];

View File

@ -1,40 +0,0 @@
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {ApplicationRef, BaseException, ComponentResolver} from '@angular/core';
import {Router, RouterOutletMap} from './router';
import {DefaultRouterUrlSerializer, RouterUrlSerializer} from './router_url_serializer';
import {RouteSegment} from './segments';
/**
* The Platform agnostic ROUTER PROVIDERS
*/
export const ROUTER_PROVIDERS_COMMON: any[] = /*@ts2dart_const*/[
RouterOutletMap,
/*@ts2dart_Provider*/ {provide: RouterUrlSerializer, useClass: DefaultRouterUrlSerializer},
/*@ts2dart_Provider*/ {provide: LocationStrategy, useClass: PathLocationStrategy}, Location,
/*@ts2dart_Provider*/ {
provide: Router,
useFactory: routerFactory,
deps: /*@ts2dart_const*/
[ApplicationRef, ComponentResolver, RouterUrlSerializer, RouterOutletMap, Location],
},
/*@ts2dart_Provider*/ {provide: RouteSegment, useFactory: routeSegmentFactory, deps: [Router]}
];
export function routerFactory(
app: ApplicationRef, componentResolver: ComponentResolver, urlSerializer: RouterUrlSerializer,
routerOutletMap: RouterOutletMap, location: Location): Router {
if (app.componentTypes.length == 0) {
throw new BaseException('Bootstrap at least one component before injecting Router.');
}
// TODO: vsavkin this should not be null
let router = new Router(
null, app.componentTypes[0], componentResolver, urlSerializer, routerOutletMap, location);
app.registerDisposeListener(() => router.dispose());
return router;
}
export function routeSegmentFactory(router: Router): RouteSegment {
return router.routeTree.root;
}

View File

@ -1,201 +0,0 @@
import {BaseException} from '@angular/core';
import {RegExpWrapper, isBlank, isPresent} from './facade/lang';
import {Tree, TreeNode, UrlSegment, UrlTree, rootNode} from './segments';
/**
* 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);
return new UrlTree(root);
}
serialize(tree: UrlTree): string { return _serializeUrlTreeNode(rootNode(tree)); }
}
function _serializeUrlTreeNode(node: TreeNode<UrlSegment>): string {
return `${node.value}${_serializeChildren(node)}`;
}
function _serializeUrlTreeNodes(nodes: TreeNode<UrlSegment>[]): string {
let main = nodes[0].value.toString();
let auxNodes = nodes.slice(1);
let aux = auxNodes.length > 0 ? `(${auxNodes.map(_serializeUrlTreeNode).join("//")})` : '';
let children = _serializeChildren(nodes[0]);
return `${main}${aux}${children}`;
}
function _serializeChildren(node: TreeNode<UrlSegment>): string {
if (node.children.length > 0) {
return `/${_serializeUrlTreeNodes(node.children)}`;
} else {
return '';
}
}
var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&#]+');
function matchUrlSegment(str: string): string {
var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
return isPresent(match) ? match[0] : '';
}
var QUERY_PARAM_VALUE_RE = RegExpWrapper.create('^[^\\(\\)\\?;&#]+');
function matchUrlQueryParamValue(str: string): string {
var match = RegExpWrapper.firstMatch(QUERY_PARAM_VALUE_RE, str);
return isPresent(match) ? match[0] : '';
}
class _UrlParser {
private _remaining: string;
peekStartsWith(str: string): boolean { return this._remaining.startsWith(str); }
capture(str: string): void {
if (!this._remaining.startsWith(str)) {
throw new BaseException(`Expected "${str}".`);
}
this._remaining = this._remaining.substring(str.length);
}
parse(url: string): TreeNode<UrlSegment> {
this._remaining = url;
if (url == '' || url == '/') {
return new TreeNode<UrlSegment>(new UrlSegment('', {}, null), []);
} else {
return this.parseRoot();
}
}
parseRoot(): TreeNode<UrlSegment> {
let segments = this.parseSegments();
return new TreeNode<UrlSegment>(new UrlSegment('', {}, null), segments);
}
parseSegments(outletName: string = null): TreeNode<UrlSegment>[] {
if (this._remaining.length == 0) {
return [];
}
if (this.peekStartsWith('/')) {
this.capture('/');
}
var path = matchUrlSegment(this._remaining);
this.capture(path);
if (path.indexOf(':') > -1) {
let parts = path.split(':');
outletName = parts[0];
path = parts[1];
}
var matrixParams: {[key: string]: any} = {};
if (this.peekStartsWith(';')) {
matrixParams = this.parseMatrixParams();
}
var aux: any[] /** TODO #9100 */ = [];
if (this.peekStartsWith('(')) {
aux = this.parseAuxiliaryRoutes();
}
var children: TreeNode<UrlSegment>[] = [];
if (this.peekStartsWith('/') && !this.peekStartsWith('//')) {
this.capture('/');
children = this.parseSegments();
}
let segment = new UrlSegment(path, matrixParams, outletName);
let node = new TreeNode<UrlSegment>(segment, children);
return [node].concat(aux);
}
parseQueryParams(): {[key: string]: any} {
var params: {[key: string]: any} = {};
this.capture('?');
this.parseQueryParam(params);
while (this._remaining.length > 0 && this.peekStartsWith('&')) {
this.capture('&');
this.parseQueryParam(params);
}
return params;
}
parseMatrixParams(): {[key: string]: any} {
var params: {[key: string]: any} = {};
while (this._remaining.length > 0 && this.peekStartsWith(';')) {
this.capture(';');
this.parseParam(params);
}
return params;
}
parseParam(params: {[key: string]: any}): void {
var key = matchUrlSegment(this._remaining);
if (isBlank(key)) {
return;
}
this.capture(key);
var value: any = 'true';
if (this.peekStartsWith('=')) {
this.capture('=');
var valueMatch = matchUrlSegment(this._remaining);
if (isPresent(valueMatch)) {
value = valueMatch;
this.capture(value);
}
}
params[key] = value;
}
parseQueryParam(params: {[key: string]: any}): void {
var key = matchUrlSegment(this._remaining);
if (isBlank(key)) {
return;
}
this.capture(key);
var value: any = 'true';
if (this.peekStartsWith('=')) {
this.capture('=');
var valueMatch = matchUrlQueryParamValue(this._remaining);
if (isPresent(valueMatch)) {
value = valueMatch;
this.capture(value);
}
}
params[key] = value;
}
parseAuxiliaryRoutes(): TreeNode<UrlSegment>[] {
var segments: any[] /** TODO #9100 */ = [];
this.capture('(');
while (!this.peekStartsWith(')') && this._remaining.length > 0) {
segments = segments.concat(this.parseSegments('aux'));
if (this.peekStartsWith('//')) {
this.capture('//');
}
}
this.capture(')');
return segments;
}
}

View File

@ -1,157 +0,0 @@
import {ComponentFactory, Type} from '@angular/core';
import {DEFAULT_OUTLET_NAME} from './constants';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {NumberWrapper, isBlank, isPresent, stringify} from './facade/lang';
export class Tree<T> {
/** @internal */
_root: TreeNode<T>;
constructor(root: TreeNode<T>) { this._root = root; }
get root(): T { return this._root.value; }
parent(t: T): T {
let p = this.pathFromRoot(t);
return p.length > 1 ? p[p.length - 2] : null;
}
children(t: T): T[] {
let n = _findNode(t, this._root);
return isPresent(n) ? n.children.map(t => t.value) : null;
}
firstChild(t: T): T {
let n = _findNode(t, this._root);
return isPresent(n) && n.children.length > 0 ? n.children[0].value : null;
}
pathFromRoot(t: T): T[] { return _findPath(t, this._root, []).map(s => s.value); }
contains(tree: Tree<T>): boolean { return _contains(this._root, tree._root); }
}
export class UrlTree extends Tree<UrlSegment> {
constructor(root: TreeNode<UrlSegment>) { super(root); }
}
export class RouteTree extends Tree<RouteSegment> {
constructor(root: TreeNode<RouteSegment>) { super(root); }
}
export function rootNode<T>(tree: Tree<T>): TreeNode<T> {
return tree._root;
}
function _findNode<T>(expected: T, c: TreeNode<T>): TreeNode<T> {
if (expected === c.value) return c;
for (let cc of c.children) {
let r = _findNode(expected, cc);
if (isPresent(r)) return r;
}
return null;
}
function _findPath<T>(expected: T, c: TreeNode<T>, collected: TreeNode<T>[]): TreeNode<T>[] {
collected.push(c);
if (expected === c.value) return collected;
for (let cc of c.children) {
let r = _findPath(expected, cc, ListWrapper.clone(collected));
if (isPresent(r)) return r;
}
return null;
}
function _contains<T>(tree: TreeNode<T>, subtree: TreeNode<T>): boolean {
if (tree.value !== subtree.value) return false;
for (let subtreeNode of subtree.children) {
let s = tree.children.filter(child => child.value === subtreeNode.value);
if (s.length === 0) return false;
if (!_contains(s[0], subtreeNode)) return false;
}
return true;
}
export class TreeNode<T> {
constructor(public value: T, public children: TreeNode<T>[]) {}
}
export class UrlSegment {
constructor(
public segment: any, public parameters: {[key: string]: string}, public outlet: string) {}
toString(): string {
let outletPrefix = isBlank(this.outlet) ? '' : `${this.outlet}:`;
return `${outletPrefix}${this.segment}${_serializeParams(this.parameters)}`;
}
}
function _serializeParams(params: {[key: string]: string}): string {
let res = '';
StringMapWrapper.forEach(
params, (v: any /** TODO #9100 */, k: any /** TODO #9100 */) => res += `;${k}=${v}`);
return res;
}
export class RouteSegment {
/** @internal */
_type: Type;
/** @internal */
_componentFactory: ComponentFactory<any>;
constructor(
public urlSegments: UrlSegment[], public parameters: {[key: string]: string},
public outlet: string, type: Type, componentFactory: ComponentFactory<any>) {
this._type = type;
this._componentFactory = componentFactory;
}
getParam(param: string): string {
return isPresent(this.parameters) ? this.parameters[param] : null;
}
getParamAsNumber(param: string): number {
return isPresent(this.parameters) ? NumberWrapper.parseFloat(this.parameters[param]) : null;
}
get type(): Type { return this._type; }
get stringifiedUrlSegments(): string { return this.urlSegments.map(s => s.toString()).join('/'); }
}
export function createEmptyRouteTree(type: Type): RouteTree {
let root = new RouteSegment([new UrlSegment('', {}, null)], {}, DEFAULT_OUTLET_NAME, type, null);
return new RouteTree(new TreeNode<RouteSegment>(root, []));
}
export function serializeRouteSegmentTree(tree: RouteTree): string {
return _serializeRouteSegmentTree(tree._root);
}
function _serializeRouteSegmentTree(node: TreeNode<RouteSegment>): string {
let v = node.value;
let children = node.children.map(c => _serializeRouteSegmentTree(c)).join(', ');
return `${v.outlet}:${v.stringifiedUrlSegments}(${stringify(v.type)}) [${children}]`;
}
export function equalUrlSegments(a: UrlSegment[], b: UrlSegment[]): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; ++i) {
if (a[i].segment != b[i].segment) return false;
if (a[i].outlet != b[i].outlet) return false;
if (!StringMapWrapper.equals(a[i].parameters, b[i].parameters)) return false;
}
return true;
}
export function routeSegmentComponentFactory(a: RouteSegment): ComponentFactory<any> {
return a._componentFactory;
}