2015-11-06 17:34:07 -08:00
|
|
|
import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
|
|
|
import {Map, StringMapWrapper, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
|
|
|
import {isBlank, isString, isPresent, Type, isArray} from 'angular2/src/facade/lang';
|
|
|
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
import {Inject, Injectable} from 'angular2/core';
|
|
|
|
|
|
|
|
import {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from './route_registry';
|
2015-09-23 00:10:26 -07:00
|
|
|
import {
|
|
|
|
ComponentInstruction,
|
|
|
|
Instruction,
|
|
|
|
} from './instruction';
|
2015-04-17 09:59:56 -07:00
|
|
|
import {RouterOutlet} from './router_outlet';
|
2015-04-21 11:23:23 -07:00
|
|
|
import {Location} from './location';
|
2015-07-07 15:44:29 -07:00
|
|
|
import {getCanActivateHook} from './route_lifecycle_reflector';
|
2015-07-13 16:12:48 -07:00
|
|
|
import {RouteDefinition} from './route_config_impl';
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-06-29 10:40:16 +02:00
|
|
|
let _resolveToTrue = PromiseWrapper.resolve(true);
|
|
|
|
let _resolveToFalse = PromiseWrapper.resolve(false);
|
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
/**
|
2015-09-03 16:17:23 -07:00
|
|
|
* The `Router` is responsible for mapping URLs to components.
|
2015-04-17 09:59:56 -07:00
|
|
|
*
|
|
|
|
* You can see the state of the router by inspecting the read-only field `router.navigating`.
|
|
|
|
* This may be useful for showing a spinner, for instance.
|
|
|
|
*
|
2015-05-14 13:01:48 -07:00
|
|
|
* ## Concepts
|
2015-09-03 16:17:23 -07:00
|
|
|
*
|
2015-05-14 13:01:48 -07:00
|
|
|
* Routers and component instances have a 1:1 correspondence.
|
|
|
|
*
|
2015-09-03 16:17:23 -07:00
|
|
|
* The router holds reference to a number of {@link RouterOutlet}.
|
|
|
|
* An outlet is a placeholder that the router dynamically fills in depending on the current URL.
|
2015-05-14 13:01:48 -07:00
|
|
|
*
|
2015-09-11 21:49:41 -07:00
|
|
|
* When the router navigates from a URL, it must first recognize it and serialize it into an
|
2015-05-29 14:58:41 -07:00
|
|
|
* `Instruction`.
|
2015-05-14 13:01:48 -07:00
|
|
|
* The router uses the `RouteRegistry` to get an `Instruction`.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
|
|
|
export class Router {
|
2015-06-29 10:37:55 +02:00
|
|
|
navigating: boolean = false;
|
2015-04-17 09:59:56 -07:00
|
|
|
lastNavigationAttempt: string;
|
2015-06-29 10:37:55 +02:00
|
|
|
|
|
|
|
private _currentInstruction: Instruction = null;
|
2015-08-30 21:25:46 -07:00
|
|
|
|
2015-06-29 10:40:16 +02:00
|
|
|
private _currentNavigation: Promise<any> = _resolveToTrue;
|
2015-06-29 10:37:55 +02:00
|
|
|
private _outlet: RouterOutlet = null;
|
2015-08-30 21:25:46 -07:00
|
|
|
|
2015-09-29 11:11:06 -07:00
|
|
|
private _auxRouters = new Map<string, Router>();
|
2015-08-30 21:25:46 -07:00
|
|
|
private _childRouter: Router;
|
|
|
|
|
2015-10-24 18:48:43 -07:00
|
|
|
private _subject: EventEmitter<any> = new EventEmitter();
|
2015-05-29 14:58:41 -07:00
|
|
|
|
2015-08-30 21:25:46 -07:00
|
|
|
|
2015-08-24 14:39:54 -07:00
|
|
|
constructor(public registry: RouteRegistry, public parent: Router, public hostComponent: any) {}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-29 14:58:41 -07:00
|
|
|
* Constructs a child router. You probably don't need to use this unless you're writing a reusable
|
|
|
|
* component.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-08-30 21:25:46 -07:00
|
|
|
childRouter(hostComponent: any): Router {
|
|
|
|
return this._childRouter = new ChildRouter(this, hostComponent);
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-08-30 21:25:46 -07:00
|
|
|
* Constructs a child router. You probably don't need to use this unless you're writing a reusable
|
|
|
|
* component.
|
|
|
|
*/
|
|
|
|
auxRouter(hostComponent: any): Router { return new ChildRouter(this, hostComponent); }
|
|
|
|
|
|
|
|
/**
|
2015-12-06 17:04:53 +09:00
|
|
|
* Register an outlet to be notified of primary route changes.
|
2015-08-30 21:25:46 -07:00
|
|
|
*
|
|
|
|
* You probably don't need to use this unless you're writing a reusable component.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-08-30 21:25:46 -07:00
|
|
|
registerPrimaryOutlet(outlet: RouterOutlet): Promise<boolean> {
|
2015-07-17 13:36:53 -07:00
|
|
|
if (isPresent(outlet.name)) {
|
2015-10-26 13:58:00 +00:00
|
|
|
throw new BaseException(`registerPrimaryOutlet expects to be called with an unnamed outlet.`);
|
2015-07-17 13:36:53 -07:00
|
|
|
}
|
2015-08-30 21:25:46 -07:00
|
|
|
|
|
|
|
this._outlet = outlet;
|
2015-05-07 21:10:12 -07:00
|
|
|
if (isPresent(this._currentInstruction)) {
|
2015-08-30 21:25:46 -07:00
|
|
|
return this.commit(this._currentInstruction, false);
|
|
|
|
}
|
|
|
|
return _resolveToTrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register an outlet to notified of auxiliary route changes.
|
|
|
|
*
|
|
|
|
* You probably don't need to use this unless you're writing a reusable component.
|
|
|
|
*/
|
|
|
|
registerAuxOutlet(outlet: RouterOutlet): Promise<boolean> {
|
|
|
|
var outletName = outlet.name;
|
|
|
|
if (isBlank(outletName)) {
|
|
|
|
throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
var router = this.auxRouter(this.hostComponent);
|
|
|
|
|
|
|
|
this._auxRouters.set(outletName, router);
|
|
|
|
router._outlet = outlet;
|
|
|
|
|
|
|
|
var auxInstruction;
|
|
|
|
if (isPresent(this._currentInstruction) &&
|
|
|
|
isPresent(auxInstruction = this._currentInstruction.auxInstruction[outletName])) {
|
|
|
|
return router.commit(auxInstruction);
|
2015-05-07 21:10:12 -07:00
|
|
|
}
|
2015-06-29 10:40:16 +02:00
|
|
|
return _resolveToTrue;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-30 21:25:46 -07:00
|
|
|
/**
|
|
|
|
* Given an instruction, returns `true` if the instruction is currently active,
|
|
|
|
* otherwise `false`.
|
|
|
|
*/
|
|
|
|
isRouteActive(instruction: Instruction): boolean {
|
2015-12-09 13:42:36 -08:00
|
|
|
var router: Router = this;
|
2015-08-30 21:25:46 -07:00
|
|
|
while (isPresent(router.parent) && isPresent(instruction.child)) {
|
|
|
|
router = router.parent;
|
|
|
|
instruction = instruction.child;
|
|
|
|
}
|
|
|
|
return isPresent(this._currentInstruction) &&
|
|
|
|
this._currentInstruction.component == instruction.component;
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
/**
|
2015-05-14 13:01:48 -07:00
|
|
|
* Dynamically update the routing configuration and trigger a navigation.
|
2015-04-17 09:59:56 -07:00
|
|
|
*
|
2015-11-17 09:41:31 -08:00
|
|
|
* ### Usage
|
2015-04-17 09:59:56 -07:00
|
|
|
*
|
|
|
|
* ```
|
2015-04-29 15:47:12 -07:00
|
|
|
* router.config([
|
|
|
|
* { 'path': '/', 'component': IndexComp },
|
|
|
|
* { 'path': '/user/:id', 'component': UserComp },
|
|
|
|
* ]);
|
|
|
|
* ```
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-08-28 11:29:19 -07:00
|
|
|
config(definitions: RouteDefinition[]): Promise<any> {
|
2015-07-17 13:36:53 -07:00
|
|
|
definitions.forEach(
|
|
|
|
(routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); });
|
2015-04-17 09:59:56 -07:00
|
|
|
return this.renavigate();
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
|
2015-09-08 21:42:23 -07:00
|
|
|
/**
|
|
|
|
* Navigate based on the provided Route Link DSL. It's preferred to navigate with this method
|
|
|
|
* over `navigateByUrl`.
|
|
|
|
*
|
2015-11-17 09:41:31 -08:00
|
|
|
* ### Usage
|
2015-09-08 21:42:23 -07:00
|
|
|
*
|
|
|
|
* This method takes an array representing the Route Link DSL:
|
|
|
|
* ```
|
|
|
|
* ['./MyCmp', {param: 3}]
|
|
|
|
* ```
|
|
|
|
* See the {@link RouterLink} directive for more.
|
|
|
|
*/
|
|
|
|
navigate(linkParams: any[]): Promise<any> {
|
|
|
|
var instruction = this.generate(linkParams);
|
|
|
|
return this.navigateByInstruction(instruction, false);
|
|
|
|
}
|
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
/**
|
2015-05-21 13:59:14 -07:00
|
|
|
* Navigate to a URL. Returns a promise that resolves when navigation is complete.
|
2015-09-08 21:42:23 -07:00
|
|
|
* It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle.
|
2015-05-14 13:01:48 -07:00
|
|
|
*
|
|
|
|
* If the given URL begins with a `/`, router will navigate absolutely.
|
|
|
|
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-09-08 21:41:56 -07:00
|
|
|
navigateByUrl(url: string, _skipLocationChange: boolean = false): Promise<any> {
|
2015-07-07 15:44:29 -07:00
|
|
|
return this._currentNavigation = this._currentNavigation.then((_) => {
|
|
|
|
this.lastNavigationAttempt = url;
|
2015-05-21 13:59:14 -07:00
|
|
|
this._startNavigating();
|
2015-07-17 13:36:53 -07:00
|
|
|
return this._afterPromiseFinishNavigating(this.recognize(url).then((instruction) => {
|
|
|
|
if (isBlank(instruction)) {
|
2015-07-07 15:44:29 -07:00
|
|
|
return false;
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
return this._navigate(instruction, _skipLocationChange);
|
2015-07-07 15:44:29 -07:00
|
|
|
}));
|
2015-05-21 13:59:14 -07:00
|
|
|
});
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigate via the provided instruction. Returns a promise that resolves when navigation is
|
|
|
|
* complete.
|
|
|
|
*/
|
2015-09-08 21:41:56 -07:00
|
|
|
navigateByInstruction(instruction: Instruction,
|
|
|
|
_skipLocationChange: boolean = false): Promise<any> {
|
2015-07-17 13:36:53 -07:00
|
|
|
if (isBlank(instruction)) {
|
|
|
|
return _resolveToFalse;
|
|
|
|
}
|
|
|
|
return this._currentNavigation = this._currentNavigation.then((_) => {
|
|
|
|
this._startNavigating();
|
|
|
|
return this._afterPromiseFinishNavigating(this._navigate(instruction, _skipLocationChange));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
2015-07-17 13:36:53 -07:00
|
|
|
_navigate(instruction: Instruction, _skipLocationChange: boolean): Promise<any> {
|
2015-08-14 12:31:56 -07:00
|
|
|
return this._settleInstruction(instruction)
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
.then((_) => this._routerCanReuse(instruction))
|
2015-07-17 13:36:53 -07:00
|
|
|
.then((_) => this._canActivate(instruction))
|
2016-01-25 17:57:29 -08:00
|
|
|
.then((result: boolean) => {
|
2015-07-17 13:36:53 -07:00
|
|
|
if (!result) {
|
|
|
|
return false;
|
|
|
|
}
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
return this._routerCanDeactivate(instruction)
|
2016-01-25 17:57:29 -08:00
|
|
|
.then((result: boolean) => {
|
2015-07-17 13:36:53 -07:00
|
|
|
if (result) {
|
|
|
|
return this.commit(instruction, _skipLocationChange)
|
|
|
|
.then((_) => {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
this._emitNavigationFinish(instruction.toRootUrl());
|
2015-07-17 13:36:53 -07:00
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
2015-08-14 12:31:56 -07:00
|
|
|
_settleInstruction(instruction: Instruction): Promise<any> {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
return instruction.resolveComponent().then((_) => {
|
|
|
|
var unsettledInstructions: Array<Promise<any>> = [];
|
|
|
|
|
2015-12-07 10:51:01 -08:00
|
|
|
if (isPresent(instruction.component)) {
|
|
|
|
instruction.component.reuse = false;
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
if (isPresent(instruction.child)) {
|
|
|
|
unsettledInstructions.push(this._settleInstruction(instruction.child));
|
|
|
|
}
|
|
|
|
|
|
|
|
StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
|
|
|
|
unsettledInstructions.push(this._settleInstruction(instruction));
|
|
|
|
});
|
|
|
|
return PromiseWrapper.all(unsettledInstructions);
|
2015-08-14 12:31:56 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-11-15 23:58:59 -08:00
|
|
|
private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); }
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-07-07 15:44:29 -07:00
|
|
|
private _afterPromiseFinishNavigating(promise: Promise<any>): Promise<any> {
|
|
|
|
return PromiseWrapper.catchError(promise.then((_) => this._finishNavigating()), (err) => {
|
|
|
|
this._finishNavigating();
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-08-30 21:25:46 -07:00
|
|
|
/*
|
|
|
|
* Recursively set reuse flags
|
|
|
|
*/
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
_routerCanReuse(instruction: Instruction): Promise<any> {
|
2015-07-07 15:44:29 -07:00
|
|
|
if (isBlank(this._outlet)) {
|
|
|
|
return _resolveToFalse;
|
|
|
|
}
|
2015-12-07 10:51:01 -08:00
|
|
|
if (isBlank(instruction.component)) {
|
|
|
|
return _resolveToTrue;
|
|
|
|
}
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
return this._outlet.routerCanReuse(instruction.component)
|
2015-07-07 15:44:29 -07:00
|
|
|
.then((result) => {
|
2015-08-30 20:37:23 -07:00
|
|
|
instruction.component.reuse = result;
|
2015-09-09 12:00:31 -07:00
|
|
|
if (result && isPresent(this._childRouter) && isPresent(instruction.child)) {
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
return this._childRouter._routerCanReuse(instruction.child);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-05-14 13:01:48 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
private _canActivate(nextInstruction: Instruction): Promise<boolean> {
|
|
|
|
return canActivateOne(nextInstruction, this._currentInstruction);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
private _routerCanDeactivate(instruction: Instruction): Promise<boolean> {
|
2015-07-07 15:44:29 -07:00
|
|
|
if (isBlank(this._outlet)) {
|
|
|
|
return _resolveToTrue;
|
|
|
|
}
|
|
|
|
var next: Promise<boolean>;
|
2015-08-30 21:25:46 -07:00
|
|
|
var childInstruction: Instruction = null;
|
|
|
|
var reuse: boolean = false;
|
|
|
|
var componentInstruction: ComponentInstruction = null;
|
|
|
|
if (isPresent(instruction)) {
|
|
|
|
childInstruction = instruction.child;
|
|
|
|
componentInstruction = instruction.component;
|
2015-12-07 10:51:01 -08:00
|
|
|
reuse = isBlank(instruction.component) || instruction.component.reuse;
|
2015-08-30 21:25:46 -07:00
|
|
|
}
|
|
|
|
if (reuse) {
|
2015-07-07 15:44:29 -07:00
|
|
|
next = _resolveToTrue;
|
|
|
|
} else {
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
next = this._outlet.routerCanDeactivate(componentInstruction);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
// TODO: aux route lifecycle hooks
|
2015-07-07 15:44:29 -07:00
|
|
|
return next.then((result) => {
|
|
|
|
if (result == false) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-08-30 21:25:46 -07:00
|
|
|
if (isPresent(this._childRouter)) {
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
return this._childRouter._routerCanDeactivate(childInstruction);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-05-14 13:01:48 -07:00
|
|
|
/**
|
2015-05-21 13:59:14 -07:00
|
|
|
* Updates this router and all descendant routers according to the given instruction
|
2015-05-14 13:01:48 -07:00
|
|
|
*/
|
2015-07-28 01:46:09 -04:00
|
|
|
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
2015-05-14 13:01:48 -07:00
|
|
|
this._currentInstruction = instruction;
|
2015-12-07 10:51:01 -08:00
|
|
|
|
2015-08-30 21:25:46 -07:00
|
|
|
var next: Promise<any> = _resolveToTrue;
|
2015-12-07 10:51:01 -08:00
|
|
|
if (isPresent(this._outlet) && isPresent(instruction.component)) {
|
2015-08-30 21:25:46 -07:00
|
|
|
var componentInstruction = instruction.component;
|
|
|
|
if (componentInstruction.reuse) {
|
|
|
|
next = this._outlet.reuse(componentInstruction);
|
|
|
|
} else {
|
|
|
|
next =
|
|
|
|
this.deactivate(instruction).then((_) => this._outlet.activate(componentInstruction));
|
|
|
|
}
|
|
|
|
if (isPresent(instruction.child)) {
|
|
|
|
next = next.then((_) => {
|
|
|
|
if (isPresent(this._childRouter)) {
|
|
|
|
return this._childRouter.commit(instruction.child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2015-05-21 13:59:14 -07:00
|
|
|
}
|
2015-08-30 21:25:46 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
var promises = [];
|
2015-10-30 17:05:30 -07:00
|
|
|
this._auxRouters.forEach((router, name) => {
|
|
|
|
if (isPresent(instruction.auxInstruction[name])) {
|
|
|
|
promises.push(router.commit(instruction.auxInstruction[name]));
|
|
|
|
}
|
|
|
|
});
|
2015-08-30 21:25:46 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
return next.then((_) => PromiseWrapper.all(promises));
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
2015-05-14 13:01:48 -07:00
|
|
|
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
2015-07-07 15:44:29 -07:00
|
|
|
_startNavigating(): void { this.navigating = true; }
|
|
|
|
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
2015-07-07 15:44:29 -07:00
|
|
|
_finishNavigating(): void { this.navigating = false; }
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Subscribe to URL updates from the router
|
|
|
|
*/
|
2015-08-18 11:30:32 -07:00
|
|
|
subscribe(onNext: (value: any) => void): Object {
|
|
|
|
return ObservableWrapper.subscribe(this._subject, onNext);
|
2015-07-07 20:03:00 -07:00
|
|
|
}
|
2015-07-07 15:44:29 -07:00
|
|
|
|
|
|
|
|
2015-05-14 13:01:48 -07:00
|
|
|
/**
|
2015-05-21 13:59:14 -07:00
|
|
|
* Removes the contents of this router's outlet and all descendant outlets
|
2015-05-14 13:01:48 -07:00
|
|
|
*/
|
2015-07-07 15:44:29 -07:00
|
|
|
deactivate(instruction: Instruction): Promise<any> {
|
2015-08-30 21:25:46 -07:00
|
|
|
var childInstruction: Instruction = null;
|
|
|
|
var componentInstruction: ComponentInstruction = null;
|
|
|
|
if (isPresent(instruction)) {
|
|
|
|
childInstruction = instruction.child;
|
|
|
|
componentInstruction = instruction.component;
|
|
|
|
}
|
|
|
|
var next: Promise<any> = _resolveToTrue;
|
|
|
|
if (isPresent(this._childRouter)) {
|
|
|
|
next = this._childRouter.deactivate(childInstruction);
|
|
|
|
}
|
2015-05-21 13:59:14 -07:00
|
|
|
if (isPresent(this._outlet)) {
|
2015-08-30 21:25:46 -07:00
|
|
|
next = next.then((_) => this._outlet.deactivate(componentInstruction));
|
2015-05-21 13:59:14 -07:00
|
|
|
}
|
2015-08-30 21:25:46 -07:00
|
|
|
|
|
|
|
// TODO: handle aux routes
|
|
|
|
|
|
|
|
return next;
|
2015-05-14 13:01:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
/**
|
|
|
|
* Given a URL, returns an instruction representing the component graph
|
|
|
|
*/
|
2015-05-21 13:59:14 -07:00
|
|
|
recognize(url: string): Promise<Instruction> {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
var ancestorComponents = this._getAncestorInstructions();
|
|
|
|
return this.registry.recognize(url, ancestorComponents);
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getAncestorInstructions(): Instruction[] {
|
2015-12-07 10:51:01 -08:00
|
|
|
var ancestorInstructions = [this._currentInstruction];
|
2015-12-09 13:42:36 -08:00
|
|
|
var ancestorRouter: Router = this;
|
2015-12-07 10:51:01 -08:00
|
|
|
while (isPresent(ancestorRouter = ancestorRouter.parent)) {
|
|
|
|
ancestorInstructions.unshift(ancestorRouter._currentInstruction);
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
}
|
2015-12-07 10:51:01 -08:00
|
|
|
return ancestorInstructions;
|
2015-05-21 13:59:14 -07:00
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-29 14:58:41 -07:00
|
|
|
* Navigates to either the last URL successfully navigated to, or the last URL requested if the
|
|
|
|
* router has yet to successfully navigate.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-05-29 14:58:41 -07:00
|
|
|
renavigate(): Promise<any> {
|
2015-07-07 15:44:29 -07:00
|
|
|
if (isBlank(this.lastNavigationAttempt)) {
|
2015-05-21 13:59:14 -07:00
|
|
|
return this._currentNavigation;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
2015-09-08 21:41:56 -07:00
|
|
|
return this.navigateByUrl(this.lastNavigationAttempt);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-19 20:01:21 -06:00
|
|
|
* Generate an `Instruction` based on the provided Route Link DSL.
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-08-28 11:29:19 -07:00
|
|
|
generate(linkParams: any[]): Instruction {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
var ancestorInstructions = this._getAncestorInstructions();
|
|
|
|
return this.registry.generate(linkParams, ancestorInstructions);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
@Injectable()
|
2015-04-17 09:59:56 -07:00
|
|
|
export class RootRouter extends Router {
|
2015-10-09 17:21:25 -07:00
|
|
|
/** @internal */
|
2015-05-29 14:58:41 -07:00
|
|
|
_location: Location;
|
2015-10-28 14:16:54 -07:00
|
|
|
/** @internal */
|
2015-10-26 11:01:02 -07:00
|
|
|
_locationSub: Object;
|
2015-05-14 13:01:48 -07:00
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
constructor(registry: RouteRegistry, location: Location,
|
|
|
|
@Inject(ROUTER_PRIMARY_COMPONENT) primaryComponent: Type) {
|
2015-09-03 12:55:08 -07:00
|
|
|
super(registry, null, primaryComponent);
|
2015-05-14 13:01:48 -07:00
|
|
|
this._location = location;
|
2015-12-07 16:05:57 -08:00
|
|
|
this._locationSub = this._location.subscribe((change) => {
|
|
|
|
// we call recognize ourselves
|
|
|
|
this.recognize(change['url'])
|
|
|
|
.then((instruction) => {
|
|
|
|
this.navigateByInstruction(instruction, isPresent(change['pop']))
|
|
|
|
.then((_) => {
|
|
|
|
// this is a popstate event; no need to change the URL
|
|
|
|
if (isPresent(change['pop']) && change['type'] != 'hashchange') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var emitPath = instruction.toUrlPath();
|
|
|
|
var emitQuery = instruction.toUrlQuery();
|
2016-01-27 21:05:34 +01:00
|
|
|
if (emitPath.length > 0 && emitPath[0] != '/') {
|
2015-12-07 16:05:57 -08:00
|
|
|
emitPath = '/' + emitPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because we've opted to use All hashchange events occur outside Angular.
|
|
|
|
// However, apps that are migrating might have hash links that operate outside
|
|
|
|
// angular to which routing must respond.
|
|
|
|
// To support these cases where we respond to hashchanges and redirect as a
|
|
|
|
// result, we need to replace the top item on the stack.
|
|
|
|
if (change['type'] == 'hashchange') {
|
|
|
|
if (instruction.toRootUrl() != this._location.path()) {
|
|
|
|
this._location.replaceState(emitPath, emitQuery);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this._location.go(emitPath, emitQuery);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-09-03 12:55:08 -07:00
|
|
|
this.registry.configFromComponent(primaryComponent);
|
2015-09-08 21:41:56 -07:00
|
|
|
this.navigateByUrl(location.path());
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
2015-05-14 13:01:48 -07:00
|
|
|
|
2015-07-28 01:46:09 -04:00
|
|
|
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 18:07:37 -08:00
|
|
|
var emitPath = instruction.toUrlPath();
|
|
|
|
var emitQuery = instruction.toUrlQuery();
|
2016-01-27 21:05:34 +01:00
|
|
|
if (emitPath.length > 0 && emitPath[0] != '/') {
|
2015-09-23 00:10:26 -07:00
|
|
|
emitPath = '/' + emitPath;
|
2015-07-17 13:36:53 -07:00
|
|
|
}
|
2015-07-28 01:46:09 -04:00
|
|
|
var promise = super.commit(instruction);
|
|
|
|
if (!_skipLocationChange) {
|
2015-09-23 00:10:26 -07:00
|
|
|
promise = promise.then((_) => { this._location.go(emitPath, emitQuery); });
|
2015-07-28 01:46:09 -04:00
|
|
|
}
|
|
|
|
return promise;
|
2015-05-14 13:01:48 -07:00
|
|
|
}
|
2015-10-26 11:01:02 -07:00
|
|
|
|
|
|
|
dispose(): void {
|
|
|
|
if (isPresent(this._locationSub)) {
|
|
|
|
ObservableWrapper.dispose(this._locationSub);
|
|
|
|
this._locationSub = null;
|
|
|
|
}
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class ChildRouter extends Router {
|
2015-05-29 14:58:41 -07:00
|
|
|
constructor(parent: Router, hostComponent) {
|
2015-08-24 14:39:54 -07:00
|
|
|
super(parent.registry, parent, hostComponent);
|
2015-04-17 09:59:56 -07:00
|
|
|
this.parent = parent;
|
|
|
|
}
|
2015-07-01 16:37:36 -07:00
|
|
|
|
|
|
|
|
2015-09-08 21:41:56 -07:00
|
|
|
navigateByUrl(url: string, _skipLocationChange: boolean = false): Promise<any> {
|
2015-07-01 16:37:36 -07:00
|
|
|
// Delegate navigation to the root router
|
2015-09-08 21:41:56 -07:00
|
|
|
return this.parent.navigateByUrl(url, _skipLocationChange);
|
2015-07-01 16:37:36 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
|
2015-09-08 21:41:56 -07:00
|
|
|
navigateByInstruction(instruction: Instruction,
|
|
|
|
_skipLocationChange: boolean = false): Promise<any> {
|
2015-07-17 13:36:53 -07:00
|
|
|
// Delegate navigation to the root router
|
2015-09-08 21:41:56 -07:00
|
|
|
return this.parent.navigateByInstruction(instruction, _skipLocationChange);
|
2015-07-17 13:36:53 -07:00
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
2015-07-06 17:41:15 -07:00
|
|
|
|
2015-07-07 15:44:29 -07:00
|
|
|
|
2015-10-28 08:59:19 +01:00
|
|
|
function canActivateOne(nextInstruction: Instruction,
|
|
|
|
prevInstruction: Instruction): Promise<boolean> {
|
2015-07-07 15:44:29 -07:00
|
|
|
var next = _resolveToTrue;
|
2015-12-07 10:51:01 -08:00
|
|
|
if (isBlank(nextInstruction.component)) {
|
|
|
|
return next;
|
|
|
|
}
|
2015-07-07 15:44:29 -07:00
|
|
|
if (isPresent(nextInstruction.child)) {
|
|
|
|
next = canActivateOne(nextInstruction.child,
|
2015-07-17 13:36:53 -07:00
|
|
|
isPresent(prevInstruction) ? prevInstruction.child : null);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
return next.then((result) => {
|
|
|
|
if (result == false) {
|
2015-07-07 15:44:29 -07:00
|
|
|
return false;
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
if (nextInstruction.component.reuse) {
|
2015-07-07 15:44:29 -07:00
|
|
|
return true;
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
var hook = getCanActivateHook(nextInstruction.component.componentType);
|
2015-07-07 15:44:29 -07:00
|
|
|
if (isPresent(hook)) {
|
2015-07-17 13:36:53 -07:00
|
|
|
return hook(nextInstruction.component,
|
|
|
|
isPresent(prevInstruction) ? prevInstruction.component : null);
|
2015-07-07 15:44:29 -07:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|