refactor(router): remove deprecated apis (#10658)

This commit is contained in:
Victor Savkin 2016-08-16 13:40:28 -07:00 committed by vikerman
parent f7ff6c5a12
commit 24e280a21a
22 changed files with 963 additions and 1589 deletions

View File

@ -7,16 +7,14 @@
*/ */
export {ExtraOptions, provideRouterConfig, provideRoutes} from './src/common_router_providers'; export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, Routes} from './src/config';
export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, RouterConfig, Routes} from './src/config';
export {RouterLink, RouterLinkWithHref} from './src/directives/router_link'; export {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
export {RouterLinkActive} from './src/directives/router_link_active'; export {RouterLinkActive} from './src/directives/router_link_active';
export {RouterOutlet} from './src/directives/router_outlet'; export {RouterOutlet} from './src/directives/router_outlet';
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './src/interfaces'; export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './src/interfaces';
export {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationExtras, NavigationStart, Router, RoutesRecognized} from './src/router'; export {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationExtras, NavigationStart, Router, RoutesRecognized} from './src/router';
export {ROUTER_DIRECTIVES, RouterModule} from './src/router_module'; export {ExtraOptions, ROUTER_DIRECTIVES, RouterModule, provideRoutes} from './src/router_module';
export {RouterOutletMap} from './src/router_outlet_map'; export {RouterOutletMap} from './src/router_outlet_map';
export {provideRouter} from './src/router_providers';
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './src/router_state'; export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './src/router_state';
export {PRIMARY_OUTLET, Params} from './src/shared'; export {PRIMARY_OUTLET, Params} from './src/shared';
export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './src/url_tree'; export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './src/url_tree';

View File

@ -285,8 +285,7 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
const noMatch = const noMatch =
{matched: false, consumedSegments: <any[]>[], lastChild: 0, positionalParamSegments: {}}; {matched: false, consumedSegments: <any[]>[], lastChild: 0, positionalParamSegments: {}};
if (route.path === '') { if (route.path === '') {
if ((route.terminal || route.pathMatch === 'full') && if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) {
(segmentGroup.hasChildren() || segments.length > 0)) {
return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
} else { } else {
return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
@ -315,7 +314,8 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
currentIndex++; currentIndex++;
} }
if (route.terminal && (segmentGroup.hasChildren() || currentIndex < segments.length)) { if (route.pathMatch === 'full' &&
(segmentGroup.hasChildren() || currentIndex < segments.length)) {
return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}};
} }
@ -434,8 +434,7 @@ function containsEmptyPathRedirects(
function emptyPathRedirect( function emptyPathRedirect(
segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean {
if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full')
(r.terminal || r.pathMatch === 'full'))
return false; return false;
return r.path === '' && r.redirectTo !== undefined; return r.path === '' && r.redirectTo !== undefined;
} }

View File

@ -1,156 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
import {Route, Routes} from './config';
import {Router} from './router';
import {ROUTER_CONFIG, ROUTES} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map';
import {ActivatedRoute} from './router_state';
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
import {flatten} from './utils/collection';
export const ROUTER_CONFIGURATION = new OpaqueToken('ROUTER_CONFIGURATION');
/**
* @experimental
*/
export interface ExtraOptions {
enableTracing?: boolean;
useHash?: boolean;
}
export function setupRouter(
ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer,
outletMap: RouterOutletMap, location: Location, injector: Injector,
loader: NgModuleFactoryLoader, compiler: Compiler, config: Route[][], opts: ExtraOptions = {}) {
if (ref.componentTypes.length == 0) {
throw new Error('Bootstrap at least one component before injecting Router.');
}
const componentType = ref.componentTypes[0];
const r = new Router(
componentType, resolver, urlSerializer, outletMap, location, injector, loader, compiler,
flatten(config));
if (opts.enableTracing) {
r.events.subscribe(e => {
console.group(`Router Event: ${(<any>e.constructor).name}`);
console.log(e.toString());
console.log(e);
console.groupEnd();
});
}
return r;
}
export function rootRoute(router: Router): ActivatedRoute {
return router.routerState.root;
}
export function initialRouterNavigation(router: Router) {
return () => { router.initialNavigation(); };
}
/**
* An array of {@link Provider}s. To use the router, you must add this to your application.
*
* ### Example
*
* ```
* @Component({directives: [ROUTER_DIRECTIVES]})
* class AppCmp {
* // ...
* }
*
* const config = [
* {path: 'home', component: Home}
* ];
*
* bootstrap(AppCmp, [provideRouter(config)]);
* ```
*
* @deprecated use RouterModule instead
*/
export function provideRouter(routes: Routes, config: ExtraOptions = {}): any[] {
return [
provideRoutes(routes),
{provide: ROUTER_CONFIGURATION, useValue: config}, Location,
{provide: LocationStrategy, useClass: PathLocationStrategy},
{provide: UrlSerializer, useClass: DefaultUrlSerializer},
{
provide: Router,
useFactory: setupRouter,
deps: [
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION
]
},
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
// Trigger initial navigation
provideRouterInitializer(), {provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}
];
}
export function provideRouterInitializer() {
return {
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: initialRouterNavigation,
deps: [Router]
};
}
/**
* Router configuration.
*
* ### Example
*
* ```
* @NgModule({providers: [
* provideRoutes([{path: 'home', component: Home}])
* ]})
* class LazyLoadedModule {
* // ...
* }
* ```
*
* @deprecated
*/
export function provideRoutes(routes: Routes): any {
return [
{provide: ANALYZE_FOR_ENTRY_COMPONENTS, multi: true, useValue: routes},
{provide: ROUTES, multi: true, useValue: routes}
];
}
/**
* Router configuration.
*
* ### Example
*
* ```
* @NgModule({providers: [
* provideRouterOptions({enableTracing: true})
* ]})
* class LazyLoadedModule {
* // ...
* }
* ```
*
* @deprecated
*/
export function provideRouterConfig(config: ExtraOptions): any {
return {provide: ROUTER_CONFIGURATION, useValue: config};
}

View File

@ -9,230 +9,6 @@
import {Type} from '@angular/core'; import {Type} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
/**
* `Routes` is an array of route configurations. Each one has the following properties:
*
* - *`path`* is a string that uses the route matcher DSL.
* - `pathMatch` is a string that specifies the matching strategy.
* - `component` is a component type.
* - `redirectTo` is the url fragment which will replace the current matched segment.
* - `outlet` is the name of the outlet the component should be placed into.
* - `canActivate` is an array of DI tokens used to look up CanActivate handlers. See {@link
* CanActivate} for more info.
* - `canDeactivate` is an array of DI tokens used to look up CanDeactivate handlers. See {@link
* CanDeactivate} for more info.
* - `data` is additional data provided to the component via `ActivatedRoute`.
* - `resolve` is a map of DI tokens used to look up data resolvers. See {@link Resolve} for more
* info.
* - `children` is an array of child route definitions.
*
* ### Simple Configuration
*
* ```
* [{
* path: 'team/:id',
* component: Team,
* children: [
* {
* path: 'user/:name',
* component: User
* }
* ]
* }]
* ```
*
* When navigating to `/team/11/user/bob`, the router will create the team component with the user
* component in it.
*
* ### Multiple Outlets
*
* ```
* [{
* path: 'team/:id',
* component: Team
* },
* {
* path: 'chat/:user',
* component: Chat
* outlet: aux
* }]
* ```
*
* When navigating to `/team/11(aux:chat/jim)`, the router will create the team component next to
* the chat component. The chat component will be placed into the aux outlet.
*
* ### Wild Cards
*
* ```
* [{
* path: '**',
* component: Sink
* }]
* ```
*
* Regardless of where you navigate to, the router will instantiate the sink component.
*
* ### Redirects
*
* ```
* [{
* path: 'team/:id',
* component: Team,
* children: [
* {
* path: 'legacy/user/:name',
* redirectTo: 'user/:name'
* },
* {
* path: 'user/:name',
* component: User
* }
* ]
* }]
* ```
*
* When navigating to '/team/11/legacy/user/jim', the router will change the url to
* '/team/11/user/jim', and then will instantiate the team component with the user component
* in it.
*
* If the `redirectTo` value starts with a '/', then it is an absolute redirect. E.g., if in the
* example above we change the `redirectTo` to `/user/:name`, the result url will be '/user/jim'.
*
* ### Empty Path
*
* Empty-path route configurations can be used to instantiate components that do not "consume"
* any url segments. Let's look at the following configuration:
*
* ```
* [{
* path: 'team/:id',
* component: Team,
* children: [
* {
* path: '',
* component: AllUsers
* },
* {
* path: 'user/:name',
* component: User
* }
* ]
* }]
* ```
*
* When navigating to `/team/11`, the router will instantiate the AllUsers component.
*
* Empty-path routes can have children.
*
* ```
* [{
* path: 'team/:id',
* component: Team,
* children: [
* {
* path: '',
* component: WrapperCmp,
* children: [
* {
* path: 'user/:name',
* component: User
* }
* ]
* }
* ]
* }]
* ```
*
* When navigating to `/team/11/user/jim`, the router will instantiate the wrapper component with
* the user component in it.
*
* ### Matching Strategy
*
* By default the router will look at what is left in the url, and check if it starts with
* the specified path (e.g., `/team/11/user` starts with `team/:id`).
*
* We can change the matching strategy to make sure that the path covers the whole unconsumed url,
* which is akin to `unconsumedUrl === path` or `$` regular expressions.
*
* This is particularly important when redirecting empty-path routes.
*
* ```
* [{
* path: '',
* pathMatch: 'prefix', //default
* redirectTo: 'main'
* },
* {
* path: 'main',
* component: Main
* }]
* ```
*
* Since an empty path is a prefix of any url, even when navigating to '/main', the router will
* still apply the redirect.
*
* If `pathMatch: full` is provided, the router will apply the redirect if and only if navigating to
* '/'.
*
* ```
* [{
* path: '',
* pathMatch: 'full',
* redirectTo: 'main'
* },
* {
* path: 'main',
* component: Main
* }]
* ```
*
* ### Componentless Routes
*
* It is useful at times to have the ability to share parameters between sibling components.
*
* Say we have two components--ChildCmp and AuxCmp--that we want to put next to each other and both
* of them require some id parameter.
*
* One way to do that would be to have a bogus parent component, so both the siblings can get the id
* parameter from it. This is not ideal. Instead, you can use a componentless route.
*
* ```
* [{
* path: 'parent/:id',
* children: [
* { path: 'a', component: MainChild },
* { path: 'b', component: AuxChild, outlet: 'aux' }
* ]
* }]
* ```
*
* So when navigating to `parent/10/(a//aux:b)`, the route will instantiate the main child and aux
* child components next to each other. In this example, the application component
* has to have the primary and aux outlets defined.
*
* The router will also merge the `params`, `data`, and `resolve` of the componentless parent into
* the `params`, `data`, and `resolve` of the children.
*
* This is especially useful when child components are defined as follows:
*
* ```
* [{
* path: 'parent/:id',
* children: [
* { path: '', component: MainChild },
* { path: '', component: AuxChild, outlet: 'aux' }
* ]
* }]
* ```
*
* With this configuration in place, navigating to '/parent/10' will create the main child and aux
* components.
*
* @deprecated use Routes
*/
export type RouterConfig = Route[];
/** /**
* `Routes` is an array of route configurations. Each one has the following properties: * `Routes` is an array of route configurations. Each one has the following properties:
* *
@ -491,13 +267,8 @@ export type LoadChildren = string | LoadChildrenCallback;
*/ */
export interface Route { export interface Route {
path?: string; path?: string;
/**
* @deprecated - use `pathMatch` instead
*/
terminal?: boolean;
pathMatch?: string; pathMatch?: string;
component?: Type<any>|string; component?: Type<any>;
redirectTo?: string; redirectTo?: string;
outlet?: string; outlet?: string;
canActivate?: any[]; canActivate?: any[];
@ -546,8 +317,7 @@ function validateNode(route: Route): void {
throw new Error( throw new Error(
`Invalid route configuration of route '${route.path}': path cannot start with a slash`); `Invalid route configuration of route '${route.path}': path cannot start with a slash`);
} }
if (route.path === '' && route.redirectTo !== undefined && if (route.path === '' && route.redirectTo !== undefined && route.pathMatch === undefined) {
(route.terminal === undefined && route.pathMatch === undefined)) {
const exp = const exp =
`The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`; `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
throw new Error( throw new Error(

View File

@ -84,24 +84,11 @@ export class RouterOutlet implements OnDestroy {
const component: any = <any>snapshot._routeConfig.component; const component: any = <any>snapshot._routeConfig.component;
let factory: ComponentFactory<any>; let factory: ComponentFactory<any>;
try { if (loadedResolver) {
if (typeof component === 'string') {
factory = snapshot._resolvedComponentFactory;
} else if (loadedResolver) {
factory = loadedResolver.resolveComponentFactory(component); factory = loadedResolver.resolveComponentFactory(component);
} else { } else {
factory = this.resolver.resolveComponentFactory(component); factory = this.resolver.resolveComponentFactory(component);
} }
} catch (e) {
if (!(e instanceof NoComponentFactoryError)) throw e;
const componentName = component ? component.name : null;
console.warn(
`'${componentName}' not found in entryComponents array. To ensure all components referred
to by the Routes are compiled, you must add '${componentName}' to the
'entryComponents' array of your application component. This will be required in a future
release of the router.`);
factory = snapshot._resolvedComponentFactory;
}
const injector = loadedInjector ? loadedInjector : this.location.parentInjector; const injector = loadedInjector ? loadedInjector : this.location.parentInjector;
const inj = ReflectiveInjector.fromResolvedProviders(providers, injector); const inj = ReflectiveInjector.fromResolvedProviders(providers, injector);

View File

@ -182,8 +182,7 @@ function match(
segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[],
parent: ActivatedRouteSnapshot) { parent: ActivatedRouteSnapshot) {
if (route.path === '') { if (route.path === '') {
if ((route.terminal || route.pathMatch === 'full') && if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
(segmentGroup.hasChildren() || segments.length > 0)) {
throw new NoMatch(); throw new NoMatch();
} else { } else {
const params = parent ? parent.params : {}; const params = parent ? parent.params : {};
@ -213,7 +212,7 @@ function match(
currentIndex++; currentIndex++;
} }
if ((route.terminal || route.pathMatch === 'full') && if (route.pathMatch === 'full' &&
(segmentGroup.hasChildren() || currentIndex < segments.length)) { (segmentGroup.hasChildren() || currentIndex < segments.length)) {
throw new NoMatch(); throw new NoMatch();
} }
@ -334,8 +333,7 @@ function containsEmptyPathMatches(
function emptyPathMatch( function emptyPathMatch(
segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean {
if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full')
(r.terminal || r.pathMatch === 'full'))
return false; return false;
return r.path === '' && r.redirectTo === undefined; return r.path === '' && r.redirectTo === undefined;
} }

View File

@ -1,49 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import {ComponentResolver} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {forkJoin} from 'rxjs/observable/forkJoin';
import {fromPromise} from 'rxjs/observable/fromPromise';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {TreeNode} from './utils/tree';
export function resolve(
resolver: ComponentResolver, state: RouterStateSnapshot): Observable<RouterStateSnapshot> {
return resolveNode(resolver, state._root).map(_ => state);
}
function resolveNode(
resolver: ComponentResolver, node: TreeNode<ActivatedRouteSnapshot>): Observable<any> {
if (node.children.length === 0) {
return fromPromise(resolveComponent(resolver, <any>node.value).then(factory => {
node.value._resolvedComponentFactory = factory;
return node.value;
}));
} else {
const c = node.children.map(c => resolveNode(resolver, c).toPromise());
return forkJoin(c).map(_ => resolveComponent(resolver, <any>node.value).then(factory => {
node.value._resolvedComponentFactory = factory;
return node.value;
}));
}
}
function resolveComponent(
resolver: ComponentResolver, snapshot: ActivatedRouteSnapshot): Promise<any> {
if (snapshot.component && snapshot._routeConfig && typeof snapshot.component === 'string') {
return resolver.resolveComponent(<any>snapshot.component);
} else {
return Promise.resolve(null);
}
}

View File

@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce';
import 'rxjs/add/operator/every'; import 'rxjs/add/operator/every';
import {Location} from '@angular/common'; import {Location} from '@angular/common';
import {Compiler, ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core'; import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject'; import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription'; import {Subscription} from 'rxjs/Subscription';
@ -26,7 +26,6 @@ import {createRouterState} from './create_router_state';
import {createUrlTree} from './create_url_tree'; import {createUrlTree} from './create_url_tree';
import {RouterOutlet} from './directives/router_outlet'; import {RouterOutlet} from './directives/router_outlet';
import {recognize} from './recognize'; import {recognize} from './recognize';
import {resolve} from './resolve';
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map'; import {RouterOutletMap} from './router_outlet_map';
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state'; import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
@ -145,10 +144,9 @@ export class Router {
* Creates the router service. * Creates the router service.
*/ */
constructor( constructor(
private rootComponentType: Type<any>, private resolver: ComponentResolver, private rootComponentType: Type<any>, private urlSerializer: UrlSerializer,
private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, private outletMap: RouterOutletMap, private location: Location, private injector: Injector,
private location: Location, private injector: Injector, loader: NgModuleFactoryLoader, loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) {
compiler: Compiler, public config: Routes) {
this.resetConfig(config); this.resetConfig(config);
this.routerEvents = new Subject<Event>(); this.routerEvents = new Subject<Event>();
this.currentUrlTree = createEmptyUrlTree(); this.currentUrlTree = createEmptyUrlTree();
@ -380,10 +378,10 @@ export class Router {
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)); this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
}) })
.mergeMap((newRouterStateSnapshot) => { .map((newRouterStateSnapshot) => {
this.routerEvents.next(new RoutesRecognized( this.routerEvents.next(new RoutesRecognized(
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot)); id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot));
return resolve(this.resolver, newRouterStateSnapshot); return newRouterStateSnapshot;
}) })
.map((routerStateSnapshot) => { .map((routerStateSnapshot) => {

View File

@ -15,11 +15,6 @@ import {LoadChildren, Route} from './config';
import {flatten, wrapIntoObservable} from './utils/collection'; import {flatten, wrapIntoObservable} from './utils/collection';
/**
* @deprecated use Routes
*/
export const ROUTER_CONFIG = new OpaqueToken('ROUTER_CONFIG');
export const ROUTES = new OpaqueToken('ROUTES'); export const ROUTES = new OpaqueToken('ROUTES');
export class LoadedRouterConfig { export class LoadedRouterConfig {

View File

@ -7,10 +7,9 @@
*/ */
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common'; import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
import {ApplicationRef, Compiler, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, ApplicationRef, Compiler, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core';
import {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRouterInitializer, provideRoutes, rootRoute, setupRouter} from './common_router_providers'; import {Route, Routes} from './config';
import {Routes} from './config';
import {RouterLink, RouterLinkWithHref} from './directives/router_link'; import {RouterLink, RouterLinkWithHref} from './directives/router_link';
import {RouterLinkActive} from './directives/router_link_active'; import {RouterLinkActive} from './directives/router_link_active';
import {RouterOutlet} from './directives/router_outlet'; import {RouterOutlet} from './directives/router_outlet';
@ -19,6 +18,7 @@ import {ROUTES} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map'; import {RouterOutletMap} from './router_outlet_map';
import {ActivatedRoute} from './router_state'; import {ActivatedRoute} from './router_state';
import {DefaultUrlSerializer, UrlSerializer} from './url_tree'; import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
import {flatten} from './utils/collection';
@ -27,6 +27,11 @@ import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
*/ */
export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkWithHref, RouterLinkActive]; export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkWithHref, RouterLinkActive];
/**
* @stable
*/
export const ROUTER_CONFIGURATION = new OpaqueToken('ROUTER_CONFIGURATION');
const pathLocationStrategy = { const pathLocationStrategy = {
provide: LocationStrategy, provide: LocationStrategy,
useClass: PathLocationStrategy useClass: PathLocationStrategy
@ -41,8 +46,8 @@ export const ROUTER_PROVIDERS: any[] = [
provide: Router, provide: Router,
useFactory: setupRouter, useFactory: setupRouter,
deps: [ deps: [
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector, ApplicationRef, UrlSerializer, RouterOutletMap, Location, Injector, NgModuleFactoryLoader,
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION Compiler, ROUTES, ROUTER_CONFIGURATION
] ]
}, },
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]}, RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
@ -103,3 +108,63 @@ export function provideLocationStrategy(
return options.useHash ? new HashLocationStrategy(platformLocationStrategy, baseHref) : return options.useHash ? new HashLocationStrategy(platformLocationStrategy, baseHref) :
new PathLocationStrategy(platformLocationStrategy, baseHref); new PathLocationStrategy(platformLocationStrategy, baseHref);
} }
/**
* @stable
*/
export function provideRoutes(routes: Routes): any {
return [
{provide: ANALYZE_FOR_ENTRY_COMPONENTS, multi: true, useValue: routes},
{provide: ROUTES, multi: true, useValue: routes}
];
}
/**
* @stable
*/
export interface ExtraOptions {
enableTracing?: boolean;
useHash?: boolean;
}
export function setupRouter(
ref: ApplicationRef, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
config: Route[][], opts: ExtraOptions = {}) {
if (ref.componentTypes.length == 0) {
throw new Error('Bootstrap at least one component before injecting Router.');
}
const componentType = ref.componentTypes[0];
const r = new Router(
componentType, urlSerializer, outletMap, location, injector, loader, compiler,
flatten(config));
if (opts.enableTracing) {
r.events.subscribe(e => {
console.group(`Router Event: ${(<any>e.constructor).name}`);
console.log(e.toString());
console.log(e);
console.groupEnd();
});
}
return r;
}
export function rootRoute(router: Router): ActivatedRoute {
return router.routerState.root;
}
export function initialRouterNavigation(router: Router) {
return () => { router.initialNavigation(); };
}
export function provideRouterInitializer() {
return {
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: initialRouterNavigation,
deps: [Router]
};
}

View File

@ -1,40 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {PlatformLocation} from '@angular/common';
import {BrowserPlatformLocation} from '@angular/platform-browser';
import {ExtraOptions, provideRouter as provideRouter_} from './common_router_providers';
import {Routes} from './config';
/**
* A list of {@link Provider}s. To use the router, you must add this to your application.
*
* ### Example
*
* ```
* @Component({directives: [ROUTER_DIRECTIVES]})
* class AppCmp {
* // ...
* }
*
* const router = [
* {path: 'home', component: Home}
* ];
*
* bootstrap(AppCmp, [provideRouter(router, {enableTracing: true})]);
* ```
*
* @experimental
*/
export function provideRouter(config: Routes, opts: ExtraOptions = {}): any[] {
return [
{provide: PlatformLocation, useClass: BrowserPlatformLocation}, ...provideRouter_(config, opts)
];
}

View File

@ -43,16 +43,6 @@ export class RouterState extends Tree<ActivatedRoute> {
setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root); setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root);
} }
/**
* @deprecated (Use root.queryParams)
*/
get queryParams(): Observable<Params> { return this.root.queryParams; }
/**
* @deprecated (Use root.fragment)
*/
get fragment(): Observable<string> { return this.root.fragment; }
toString(): string { return this.snapshot.toString(); } toString(): string { return this.snapshot.toString(); }
} }
@ -174,11 +164,6 @@ export class InheritedResolve {
* @stable * @stable
*/ */
export class ActivatedRouteSnapshot { export class ActivatedRouteSnapshot {
/**
* @internal
*/
_resolvedComponentFactory: ComponentFactory<any>;
/** @internal **/ /** @internal **/
_routeConfig: Route; _routeConfig: Route;
@ -251,16 +236,6 @@ export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
setRouterStateSnapshot<RouterStateSnapshot, ActivatedRouteSnapshot>(this, root); setRouterStateSnapshot<RouterStateSnapshot, ActivatedRouteSnapshot>(this, root);
} }
/**
* @deprecated (Use root.queryParams)
*/
get queryParams(): Params { return this.root.queryParams; }
/**
* @deprecated (Use root.fragment)
*/
get fragment(): string { return this.root.fragment; }
toString(): string { return serializeNode(this._root); } toString(): string { return serializeNode(this._root); }
} }

View File

@ -15,7 +15,7 @@ export class Tree<T> {
get root(): T { return this._root.value; } get root(): T { return this._root.value; }
/** /**
* @deprecated (use ActivatedRoute.parent instead) * @internal
*/ */
parent(t: T): T { parent(t: T): T {
const p = this.pathFromRoot(t); const p = this.pathFromRoot(t);
@ -23,7 +23,7 @@ export class Tree<T> {
} }
/** /**
* @deprecated (use ActivatedRoute.children instead) * @internal
*/ */
children(t: T): T[] { children(t: T): T[] {
const n = findNode(t, this._root); const n = findNode(t, this._root);
@ -31,7 +31,7 @@ export class Tree<T> {
} }
/** /**
* @deprecated (use ActivatedRoute.firstChild instead) * @internal
*/ */
firstChild(t: T): T { firstChild(t: T): T {
const n = findNode(t, this._root); const n = findNode(t, this._root);
@ -39,7 +39,7 @@ export class Tree<T> {
} }
/** /**
* @deprecated * @internal
*/ */
siblings(t: T): T[] { siblings(t: T): T[] {
const p = findPath(t, this._root, []); const p = findPath(t, this._root, []);
@ -50,7 +50,7 @@ export class Tree<T> {
} }
/** /**
* @deprecated (use ActivatedRoute.pathFromRoot instead) * @internal
*/ */
pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); } pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); }
} }

View File

@ -414,7 +414,7 @@ describe('applyRedirects', () => {
children: [ children: [
{path: 'b', component: ComponentB}, {path: '', redirectTo: 'b'}, {path: 'b', component: ComponentB}, {path: '', redirectTo: 'b'},
{path: 'c', component: ComponentC, outlet: 'aux'}, {path: 'c', component: ComponentC, outlet: 'aux'},
{path: '', terminal: true, redirectTo: 'c', outlet: 'aux'} {path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
] ]
}], }],
'a', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); }); 'a', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); });

View File

@ -52,7 +52,7 @@ describe('config', () => {
it('should throw when path is missing', () => { it('should throw when path is missing', () => {
expect(() => { expect(() => {
validateConfig([{component: '', redirectTo: 'b'}]); validateConfig([{component: null, redirectTo: 'b'}]);
}).toThrowError(`Invalid route configuration: routes must have path specified`); }).toThrowError(`Invalid route configuration: routes must have path specified`);
}); });
@ -76,7 +76,7 @@ describe('config', () => {
}); });
it('should throw when pathPatch is invalid', () => { it('should throw when pathPatch is invalid', () => {
expect(() => { validateConfig([{path: 'a', pathMatch: 'invalid', component: 'b'}]); }) expect(() => { validateConfig([{path: 'a', pathMatch: 'invalid', component: ComponentB}]); })
.toThrowError( .toThrowError(
/Invalid configuration of route 'a': pathMatch can only be set to 'prefix' or 'full'/); /Invalid configuration of route 'a': pathMatch can only be set to 'prefix' or 'full'/);
}); });

View File

@ -10,8 +10,8 @@ import 'rxjs/add/operator/map';
import {Location} from '@angular/common'; import {Location} from '@angular/common';
import {Component, NgModule, NgModuleFactoryLoader} from '@angular/core'; import {Component, NgModule, NgModuleFactoryLoader} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing'; import {ComponentFixture, TestBed, fakeAsync, getTestBed, inject, tick} from '@angular/core/testing';
import {TestComponentBuilder, addProviders} from '@angular/core/testing/testing_internal'; import {addProviders} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {of } from 'rxjs/observable/of'; import {of } from 'rxjs/observable/of';
@ -26,19 +26,33 @@ describe('Integration', () => {
providers: [provideRoutes( providers: [provideRoutes(
[{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])], [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])],
declarations: [ declarations: [
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp, BlankCmp,
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp, SimpleCmp,
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf, TeamCmp,
ComponentRecordingRoutePathAndUrl, RouteCmp UserCmp,
StringLinkCmp,
DummyLinkCmp,
AbsoluteLinkCmp,
RelativeLinkCmp,
DummyLinkWithParentCmp,
LinkWithQueryParamsAndFragment,
CollectParamsCmp,
QueryParamsAndFragmentCmp,
StringLinkButtonCmp,
WrapperCmp,
LinkInNgIf,
ComponentRecordingRoutePathAndUrl,
RouteCmp,
RootCmp,
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets
] ]
}); });
}); });
it('should navigate with a provided config', it('should navigate with a provided config',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.navigateByUrl('/simple'); router.navigateByUrl('/simple');
advance(fixture); advance(fixture);
@ -47,10 +61,8 @@ describe('Integration', () => {
}))); })));
it('should work when an outlet is in an ngIf', it('should work when an outlet is in an ngIf',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'child', path: 'child',
@ -64,10 +76,8 @@ describe('Integration', () => {
expect(location.path()).toEqual('/child/simple'); expect(location.path()).toEqual('/child/simple');
}))); })));
it('should work when an outlet is in an ngIf (and is removed)', it('should work when an outlet is in an ngIf (and is removed)', fakeAsync(() => {
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({ @Component({
selector: 'someRoot', selector: 'someRoot',
template: `<div *ngIf="cond"><router-outlet></router-outlet></div>`, template: `<div *ngIf="cond"><router-outlet></router-outlet></div>`,
@ -76,8 +86,12 @@ describe('Integration', () => {
class RootCmpWithLink { class RootCmpWithLink {
cond: boolean = true; cond: boolean = true;
} }
TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
const fixture = createRoot(tcb, router, RootCmpWithLink); const router: Router = getTestBed().get(Router);
const location: Location = getTestBed().get(Location);
const fixture = createRoot(router, RootCmpWithLink);
router.resetConfig( router.resetConfig(
[{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]); [{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]);
@ -94,13 +108,11 @@ describe('Integration', () => {
router.navigateByUrl('/blank').catch(e => recordedError = e); router.navigateByUrl('/blank').catch(e => recordedError = e);
advance(fixture); advance(fixture);
expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\''); expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\'');
}))); }));
it('should update location when navigating', it('should update location when navigating',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'team/:id', component: TeamCmp}]); router.resetConfig([{path: 'team/:id', component: TeamCmp}]);
@ -115,10 +127,8 @@ describe('Integration', () => {
}))); })));
it('should skip location update when using NavigationExtras.skipLocationChange with navigateByUrl', it('should skip location update when using NavigationExtras.skipLocationChange with navigateByUrl',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = TestBed.createComponent(RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture); advance(fixture);
router.resetConfig([{path: 'team/:id', component: TeamCmp}]); router.resetConfig([{path: 'team/:id', component: TeamCmp}]);
@ -138,10 +148,8 @@ describe('Integration', () => {
}))); })));
it('should skip location update when using NavigationExtras.skipLocationChange with navigate', it('should skip location update when using NavigationExtras.skipLocationChange with navigate',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = TestBed.createComponent(RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture); advance(fixture);
router.resetConfig([{path: 'team/:id', component: TeamCmp}]); router.resetConfig([{path: 'team/:id', component: TeamCmp}]);
@ -161,17 +169,14 @@ describe('Integration', () => {
}))); })));
it('should navigate back and forward', it('should navigate back and forward',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children:
{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp} [{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}]
]
}]); }]);
@ -192,10 +197,8 @@ describe('Integration', () => {
}))); })));
it('should navigate when locations changes', it('should navigate when locations changes',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -227,10 +230,8 @@ describe('Integration', () => {
}))); })));
it('should update the location when the matched route does not change', it('should update the location when the matched route does not change',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: '**', component: CollectParamsCmp}]); router.resetConfig([{path: '**', component: CollectParamsCmp}]);
@ -249,10 +250,8 @@ describe('Integration', () => {
expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']); expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']);
}))); })));
it('should support secondary routes', it('should support secondary routes', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -270,10 +269,8 @@ describe('Integration', () => {
.toHaveText('team 22 [ user victor, right: simple ]'); .toHaveText('team 22 [ user victor, right: simple ]');
}))); })));
it('should deactivate outlets', it('should deactivate outlets', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -290,14 +287,11 @@ describe('Integration', () => {
router.navigateByUrl('/team/22/user/victor'); router.navigateByUrl('/team/22/user/victor');
advance(fixture); advance(fixture);
expect(fixture.debugElement.nativeElement) expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ user victor, right: ]');
.toHaveText('team 22 [ user victor, right: ]');
}))); })));
it('should deactivate nested outlets', it('should deactivate nested outlets', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([ router.resetConfig([
{ {
@ -320,10 +314,8 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText(''); expect(fixture.debugElement.nativeElement).toHaveText('');
}))); })));
it('should set query params and fragment', it('should set query params and fragment', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'query', component: QueryParamsAndFragmentCmp}]); router.resetConfig([{path: 'query', component: QueryParamsAndFragmentCmp}]);
@ -336,10 +328,8 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2'); expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2');
}))); })));
it('should push params only when they change', it('should push params only when they change', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -362,10 +352,8 @@ describe('Integration', () => {
expect(user.recordedParams).toEqual([{name: 'victor'}, {name: 'fedor'}]); expect(user.recordedParams).toEqual([{name: 'victor'}, {name: 'fedor'}]);
}))); })));
it('should work when navigating to /', it('should work when navigating to /', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([ router.resetConfig([
{path: '', pathMatch: 'full', component: SimpleCmp}, {path: '', pathMatch: 'full', component: SimpleCmp},
@ -383,10 +371,8 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('simple'); expect(fixture.debugElement.nativeElement).toHaveText('simple');
}))); })));
it('should cancel in-flight navigations', it('should cancel in-flight navigations', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'user/:name', component: UserCmp}]); router.resetConfig([{path: 'user/:name', component: UserCmp}]);
@ -420,10 +406,8 @@ describe('Integration', () => {
]); ]);
}))); })));
it('should handle failed navigations gracefully', it('should handle failed navigations gracefully', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{path: 'user/:name', component: UserCmp}]); router.resetConfig([{path: 'user/:name', component: UserCmp}]);
@ -449,17 +433,14 @@ describe('Integration', () => {
}))); })));
it('should replace state when path is equal to current path', it('should replace state when path is equal to current path',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children:
{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp} [{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}]
]
}]); }]);
router.navigateByUrl('/team/33/simple'); router.navigateByUrl('/team/33/simple');
@ -477,10 +458,8 @@ describe('Integration', () => {
}))); })));
it('should handle componentless paths', it('should handle componentless paths',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmpWithTwoOutlets);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmpWithTwoOutlets);
router.resetConfig([ router.resetConfig([
{ {
@ -505,8 +484,7 @@ describe('Integration', () => {
router.navigateByUrl('/parent/22/(simple//right:user/fedor)'); router.navigateByUrl('/parent/22/(simple//right:user/fedor)');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/parent/22/(simple//right:user/fedor)'); expect(location.path()).toEqual('/parent/22/(simple//right:user/fedor)');
expect(fixture.debugElement.nativeElement) expect(fixture.debugElement.nativeElement).toHaveText('primary [simple] right [user fedor]');
.toHaveText('primary [simple] right [user fedor]');
// navigate to a normal route (check deactivation) // navigate to a normal route (check deactivation)
router.navigateByUrl('/user/victor'); router.navigateByUrl('/user/victor');
@ -522,10 +500,7 @@ describe('Integration', () => {
.toHaveText('primary [simple] right [user victor]'); .toHaveText('primary [simple] right [user victor]');
}))); })));
it('should emit an event when an outlet gets activated', it('should emit an event when an outlet gets activated', fakeAsync(() => {
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({ @Component({
selector: 'container', selector: 'container',
template: template:
@ -540,7 +515,11 @@ describe('Integration', () => {
recordDeactivate(component: any): void { this.deactivations.push(component); } recordDeactivate(component: any): void { this.deactivations.push(component); }
} }
const fixture = createRoot(tcb, router, Container); TestBed.configureTestingModule({declarations: [Container]});
const router: Router = getTestBed().get(Router);
const fixture = createRoot(router, Container);
const cmp = fixture.debugElement.componentInstance; const cmp = fixture.debugElement.componentInstance;
router.resetConfig( router.resetConfig(
@ -562,14 +541,12 @@ describe('Integration', () => {
expect(cmp.activations[1] instanceof SimpleCmp).toBe(true); expect(cmp.activations[1] instanceof SimpleCmp).toBe(true);
expect(cmp.deactivations.length).toEqual(2); expect(cmp.deactivations.length).toEqual(2);
expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true); expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true);
}))); }));
it('should update url and router state before activating components', it('should update url and router state before activating components',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'cmp', component: ComponentRecordingRoutePathAndUrl}]); router.resetConfig([{path: 'cmp', component: ComponentRecordingRoutePathAndUrl}]);
@ -596,18 +573,15 @@ describe('Integration', () => {
}); });
it('should provide resolved data', it('should provide resolved data',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmpWithTwoOutlets);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmpWithTwoOutlets);
router.resetConfig([{ router.resetConfig([{
path: 'parent/:id', path: 'parent/:id',
data: {one: 1}, data: {one: 1},
resolve: {two: 'resolveTwo'}, resolve: {two: 'resolveTwo'},
children: [ children: [
{path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp}, {path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp}, {
{
path: '', path: '',
data: {five: 5}, data: {five: 5},
resolve: {six: 'resolveSix'}, resolve: {six: 'resolveSix'},
@ -645,17 +619,14 @@ describe('Integration', () => {
}); });
describe('router links', () => { describe('router links', () => {
it('should support string router links', it('should support string router links', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children: [
{path: 'link', component: StringLinkCmp}, {path: 'link', component: StringLinkCmp}, {path: 'simple', component: SimpleCmp}
{path: 'simple', component: SimpleCmp}
] ]
}]); }]);
@ -671,9 +642,7 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]');
}))); })));
it('should not preserve query params and fragment by default', it('should not preserve query params and fragment by default', fakeAsync(() => {
fakeAsync(
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
@Component({ @Component({
selector: 'someRoot', selector: 'someRoot',
template: `<router-outlet></router-outlet><a routerLink="/home">Link</a>`, template: `<router-outlet></router-outlet><a routerLink="/home">Link</a>`,
@ -682,7 +651,10 @@ describe('Integration', () => {
class RootCmpWithLink { class RootCmpWithLink {
} }
const fixture = createRoot(tcb, router, RootCmpWithLink); TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
const router: Router = getTestBed().get(Router);
const fixture = createRoot(router, RootCmpWithLink);
router.resetConfig([{path: 'home', component: SimpleCmp}]); router.resetConfig([{path: 'home', component: SimpleCmp}]);
@ -691,10 +663,9 @@ describe('Integration', () => {
router.navigateByUrl('/home?q=123#fragment'); router.navigateByUrl('/home?q=123#fragment');
advance(fixture); advance(fixture);
expect(native.getAttribute('href')).toEqual('/home'); expect(native.getAttribute('href')).toEqual('/home');
}))); }));
it('should update hrefs when query params or fragment change', it('should update hrefs when query params or fragment change', fakeAsync(() => {
fakeAsync(inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
@Component({ @Component({
selector: 'someRoot', selector: 'someRoot',
@ -704,8 +675,9 @@ describe('Integration', () => {
}) })
class RootCmpWithLink { class RootCmpWithLink {
} }
TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
const fixture = createRoot(tcb, router, RootCmpWithLink); const router: Router = getTestBed().get(Router);
const fixture = createRoot(router, RootCmpWithLink);
router.resetConfig([{path: 'home', component: SimpleCmp}]); router.resetConfig([{path: 'home', component: SimpleCmp}]);
@ -722,12 +694,10 @@ describe('Integration', () => {
router.navigateByUrl('/home?q=456#1'); router.navigateByUrl('/home?q=456#1');
advance(fixture); advance(fixture);
expect(native.getAttribute('href')).toEqual('/home?q=456#1'); expect(native.getAttribute('href')).toEqual('/home?q=456#1');
}))); }));
it('should support using links on non-a tags', it('should support using links on non-a tags', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -749,17 +719,14 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]');
}))); })));
it('should support absolute router links', it('should support absolute router links', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children: [
{path: 'link', component: AbsoluteLinkCmp}, {path: 'link', component: AbsoluteLinkCmp}, {path: 'simple', component: SimpleCmp}
{path: 'simple', component: SimpleCmp}
] ]
}]); }]);
@ -775,17 +742,14 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]');
}))); })));
it('should support relative router links', it('should support relative router links', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RootCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children: [
{path: 'link', component: RelativeLinkCmp}, {path: 'link', component: RelativeLinkCmp}, {path: 'simple', component: SimpleCmp}
{path: 'simple', component: SimpleCmp}
] ]
}]); }]);
@ -801,10 +765,8 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]'); expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]');
}))); })));
it('should support top-level link', it('should support top-level link', fakeAsync(inject([Router], (router: Router) => {
fakeAsync( const fixture = createRoot(router, RelativeLinkInIfCmp);
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RelativeLinkInIfCmp);
advance(fixture); advance(fixture);
router.resetConfig( router.resetConfig(
@ -829,10 +791,8 @@ describe('Integration', () => {
}))); })));
it('should support query params and fragments', it('should support query params and fragments',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, Location, TestComponentBuilder], const fixture = createRoot(router, RootCmp);
(router: Router, location: Location, tcb: TestComponentBuilder) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -858,14 +818,11 @@ describe('Integration', () => {
}); });
describe('redirects', () => { describe('redirects', () => {
it('should work', fakeAsync(inject( it('should work', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([ router.resetConfig([
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
{path: 'team/:id', component: TeamCmp}
]); ]);
router.navigateByUrl('old/team/22'); router.navigateByUrl('old/team/22');
@ -875,14 +832,11 @@ describe('Integration', () => {
}))); })));
it('should not break the back button when trigger by location change', it('should not break the back button when trigger by location change',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = TestBed.createComponent(RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture); advance(fixture);
router.resetConfig([ router.resetConfig([
{path: 'initial', component: BlankCmp}, {path: 'initial', component: BlankCmp}, {path: 'old/team/:id', redirectTo: 'team/:id'},
{path: 'old/team/:id', redirectTo: 'team/:id'},
{path: 'team/:id', component: TeamCmp} {path: 'team/:id', component: TeamCmp}
]); ]);
@ -920,11 +874,8 @@ describe('Integration', () => {
addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]); addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]);
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysFalse']}]); [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysFalse']}]);
@ -943,10 +894,9 @@ describe('Integration', () => {
addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]); addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]);
}); });
it('works', fakeAsync(inject( it('works',
[Router, TestComponentBuilder, Location], fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
(router: Router, tcb: TestComponentBuilder, location: Location) => { const fixture = createRoot(router, RootCmp);
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'parent', path: 'parent',
@ -969,11 +919,8 @@ describe('Integration', () => {
}]); }]);
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]); [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]);
@ -994,10 +941,8 @@ describe('Integration', () => {
beforeEach(() => { addProviders([AlwaysTrue]); }); beforeEach(() => { addProviders([AlwaysTrue]); });
it('works', fakeAsync(inject( it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]); [{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]);
@ -1018,11 +963,8 @@ describe('Integration', () => {
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]);
@ -1048,11 +990,8 @@ describe('Integration', () => {
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]);
@ -1093,15 +1032,11 @@ describe('Integration', () => {
]); ]);
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([ router.resetConfig(
{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivateTeam']} [{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivateTeam']}]);
]);
router.navigateByUrl('/team/22'); router.navigateByUrl('/team/22');
advance(fixture); advance(fixture);
@ -1121,10 +1056,8 @@ describe('Integration', () => {
}))); })));
it('works (componentless route)', it('works (componentless route)',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'parent/:id', path: 'parent/:id',
@ -1146,20 +1079,15 @@ describe('Integration', () => {
}))); })));
it('works with a nested route', it('works with a nested route',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
component: TeamCmp, component: TeamCmp,
children: [ children: [
{path: '', pathMatch: 'full', component: SimpleCmp}, { {path: '', pathMatch: 'full', component: SimpleCmp},
path: 'user/:name', {path: 'user/:name', component: UserCmp, canDeactivate: ['CanDeactivateUser']}
component: UserCmp,
canDeactivate: ['CanDeactivateUser']
}
] ]
}]); }]);
@ -1192,11 +1120,8 @@ describe('Integration', () => {
beforeEach(() => { addProviders([AlwaysTrue]); }); beforeEach(() => { addProviders([AlwaysTrue]); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue]}]); [{path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue]}]);
@ -1222,11 +1147,8 @@ describe('Integration', () => {
}]); }]);
}); });
it('works', it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
fakeAsync(inject( const fixture = createRoot(router, RootCmp);
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig( router.resetConfig(
[{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]); [{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]);
@ -1251,10 +1173,8 @@ describe('Integration', () => {
}]); }]);
}); });
it('works', fakeAsync(inject( it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: '', path: '',
@ -1286,9 +1206,8 @@ describe('Integration', () => {
it('works', it('works',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location, (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
@Component({selector: 'lazy', template: 'lazy-loaded'}) @Component({selector: 'lazy', template: 'lazy-loaded'})
class LazyLoadedComponent { class LazyLoadedComponent {
@ -1304,7 +1223,7 @@ describe('Integration', () => {
} }
loader.stubbedModules = {lazyFalse: LoadedModule, lazyTrue: LoadedModule}; loader.stubbedModules = {lazyFalse: LoadedModule, lazyTrue: LoadedModule};
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([ router.resetConfig([
{path: 'lazyFalse', canLoad: ['alwaysFalse'], loadChildren: 'lazyFalse'}, {path: 'lazyFalse', canLoad: ['alwaysFalse'], loadChildren: 'lazyFalse'},
@ -1345,10 +1264,8 @@ describe('Integration', () => {
describe('routerActiveLink', () => { describe('routerActiveLink', () => {
it('should set the class when the link is active (a tag)', it('should set the class when the link is active (a tag)',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -1377,10 +1294,7 @@ describe('Integration', () => {
expect(nativeButton.className).toEqual(''); expect(nativeButton.className).toEqual('');
}))); })));
it('should not set the class until the first navigation succeeds', it('should not set the class until the first navigation succeeds', fakeAsync(() => {
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({ @Component({
template: template:
'<router-outlet></router-outlet><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" >' '<router-outlet></router-outlet><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" >'
@ -1388,7 +1302,10 @@ describe('Integration', () => {
class RootCmpWithLink { class RootCmpWithLink {
} }
const f = tcb.createFakeAsync(RootCmpWithLink); TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
const router: Router = getTestBed().get(Router);
const f = TestBed.createComponent(RootCmpWithLink);
advance(f); advance(f);
const link = f.debugElement.nativeElement.querySelector('a'); const link = f.debugElement.nativeElement.querySelector('a');
@ -1398,14 +1315,12 @@ describe('Integration', () => {
advance(f); advance(f);
expect(link.className).toEqual('active'); expect(link.className).toEqual('active');
}))); }));
it('should set the class on a parent element when the link is active', it('should set the class on a parent element when the link is active',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -1432,10 +1347,8 @@ describe('Integration', () => {
}))); })));
it('should set the class when the link is active', it('should set the class when the link is active',
fakeAsync(inject( fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
[Router, TestComponentBuilder, Location], const fixture = createRoot(router, RootCmp);
(router: Router, tcb: TestComponentBuilder, location: Location) => {
const fixture = createRoot(tcb, router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'team/:id', path: 'team/:id',
@ -1465,9 +1378,8 @@ describe('Integration', () => {
describe('lazy loading', () => { describe('lazy loading', () => {
it('works', fakeAsync(inject( it('works', fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location, (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
@Component({ @Component({
selector: 'lazy', selector: 'lazy',
template: 'lazy-loaded-parent [<router-outlet></router-outlet>]', template: 'lazy-loaded-parent [<router-outlet></router-outlet>]',
@ -1495,7 +1407,7 @@ describe('Integration', () => {
loader.stubbedModules = {expected: LoadedModule}; loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
@ -1509,9 +1421,8 @@ describe('Integration', () => {
it('should combine routes from multiple modules into a single configuration', it('should combine routes from multiple modules into a single configuration',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location, (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
@Component({selector: 'lazy', template: 'lazy-loaded-2'}) @Component({selector: 'lazy', template: 'lazy-loaded-2'})
class LazyComponent2 { class LazyComponent2 {
} }
@ -1542,7 +1453,7 @@ describe('Integration', () => {
loader.stubbedModules = {expected1: LoadedModule, expected2: SiblingOfLoadedModule}; loader.stubbedModules = {expected1: LoadedModule, expected2: SiblingOfLoadedModule};
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([ router.resetConfig([
{path: 'lazy1', loadChildren: 'expected1'}, {path: 'lazy1', loadChildren: 'expected1'},
@ -1560,9 +1471,8 @@ describe('Integration', () => {
it('should use the injector of the lazily-loaded configuration', it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location, (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
class LazyLoadedServiceDefinedInModule {} class LazyLoadedServiceDefinedInModule {}
class LazyLoadedServiceDefinedInCmp {} class LazyLoadedServiceDefinedInCmp {}
@ -1598,7 +1508,7 @@ describe('Integration', () => {
loader.stubbedModules = {expected: LoadedModule}; loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
@ -1611,8 +1521,7 @@ describe('Integration', () => {
it('works when given a callback', it('works when given a callback',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader], (router: Router, location: Location) => {
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({selector: 'lazy', template: 'lazy-loaded'}) @Component({selector: 'lazy', template: 'lazy-loaded'})
class LazyLoadedComponent { class LazyLoadedComponent {
} }
@ -1625,7 +1534,7 @@ describe('Integration', () => {
class LoadedModule { class LoadedModule {
} }
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]); router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);
@ -1638,11 +1547,10 @@ describe('Integration', () => {
it('error emit an error when cannot load a config', it('error emit an error when cannot load a config',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, tcb: TestComponentBuilder, location: Location, (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {}; loader.stubbedModules = {};
const fixture = createRoot(tcb, router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'invalid'}]); router.resetConfig([{path: 'lazy', loadChildren: 'invalid'}]);
@ -1791,9 +1699,9 @@ class QueryParamsAndFragmentCmp {
name: Observable<string>; name: Observable<string>;
fragment: Observable<string>; fragment: Observable<string>;
constructor(router: Router) { constructor(route: ActivatedRoute) {
this.name = router.routerState.queryParams.map(p => p['name']); this.name = route.queryParams.map(p => p['name']);
this.fragment = router.routerState.fragment; this.fragment = route.fragment;
} }
} }
@ -1876,8 +1784,8 @@ function advance(fixture: ComponentFixture<any>): void {
fixture.detectChanges(); fixture.detectChanges();
} }
function createRoot(tcb: TestComponentBuilder, router: Router, type: any): ComponentFixture<any> { function createRoot(router: Router, type: any): ComponentFixture<any> {
const f = tcb.createFakeAsync(type); const f = TestBed.createComponent(type);
advance(f); advance(f);
router.initialNavigation(); router.initialNavigation();
advance(f); advance(f);

View File

@ -631,13 +631,13 @@ describe('recognize', () => {
it('should support query params', () => { it('should support query params', () => {
const config = [{path: 'a', component: ComponentA}]; const config = [{path: 'a', component: ComponentA}];
checkRecognize(config, 'a?q=11', (s: RouterStateSnapshot) => { checkRecognize(config, 'a?q=11', (s: RouterStateSnapshot) => {
expect(s.queryParams).toEqual({q: '11'}); expect(s.root.queryParams).toEqual({q: '11'});
}); });
}); });
it('should freeze query params object', () => { it('should freeze query params object', () => {
checkRecognize([{path: 'a', component: ComponentA}], 'a?q=11', (s: RouterStateSnapshot) => { checkRecognize([{path: 'a', component: ComponentA}], 'a?q=11', (s: RouterStateSnapshot) => {
expect(Object.isFrozen(s.queryParams)).toBeTruthy(); expect(Object.isFrozen(s.root.queryParams)).toBeTruthy();
}); });
}); });
}); });
@ -646,7 +646,7 @@ describe('recognize', () => {
it('should support fragment', () => { it('should support fragment', () => {
const config = [{path: 'a', component: ComponentA}]; const config = [{path: 'a', component: ComponentA}];
checkRecognize( checkRecognize(
config, 'a#f1', (s: RouterStateSnapshot) => { expect(s.fragment).toEqual('f1'); }); config, 'a#f1', (s: RouterStateSnapshot) => { expect(s.root.fragment).toEqual('f1'); });
}); });
}); });

View File

@ -1,56 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Routes} from '../src/config';
import {recognize} from '../src/recognize';
import {resolve} from '../src/resolve';
import {RouterStateSnapshot} from '../src/router_state';
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree';
describe('resolve', () => {
it('should resolve components', () => {
checkResolve(
[{path: 'a', component: 'ComponentA'}], 'a', {ComponentA: 'ResolvedComponentA'},
(resolved: RouterStateSnapshot) => {
expect(resolved.firstChild(resolved.root)._resolvedComponentFactory)
.toEqual('ResolvedComponentA');
});
});
it('should not resolve componentless routes', () => {
checkResolve([{path: 'a', children: []}], 'a', {}, (resolved: RouterStateSnapshot) => {
expect(resolved.firstChild(resolved.root)._resolvedComponentFactory).toEqual(null);
});
});
});
function checkResolve(
config: Routes, url: string, resolved: {[k: string]: string}, callback: any): void {
const resolver = {
resolveComponent: (component: string): Promise<any> => {
if (resolved[component]) {
return Promise.resolve(resolved[component]);
} else {
return Promise.reject('unknown component');
}
}
};
recognize(RootComponent, config, tree(url), url)
.mergeMap(s => resolve(<any>resolver, s))
.subscribe(callback, e => { throw e; });
}
function tree(url: string): UrlTree {
return new DefaultUrlSerializer().parse(url);
}
class RootComponent {}
class ComponentA {}
class ComponentB {}
class ComponentC {}

View File

@ -8,7 +8,7 @@
import {Location, LocationStrategy} from '@angular/common'; import {Location, LocationStrategy} from '@angular/common';
import {MockLocationStrategy, SpyLocation} from '@angular/common/testing'; import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
import {Compiler, ComponentResolver, Injectable, Injector, NgModule, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core'; import {Compiler, Injectable, Injector, NgModule, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
import {Route, Router, RouterOutletMap, UrlSerializer} from '../index'; import {Route, Router, RouterOutletMap, UrlSerializer} from '../index';
import {ROUTES} from '../src/router_config_loader'; import {ROUTES} from '../src/router_config_loader';
@ -39,12 +39,10 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
} }
function setupTestingRouter( function setupTestingRouter(
resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location,
location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) {
routes: Route[][]) {
return new Router( return new Router(
null, resolver, urlSerializer, outletMap, location, injector, loader, compiler, null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(routes));
flatten(routes));
} }
/** /**
@ -74,8 +72,7 @@ function setupTestingRouter(
provide: Router, provide: Router,
useFactory: setupTestingRouter, useFactory: setupTestingRouter,
deps: [ deps: [
ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, Compiler, Injector, ROUTES
Compiler, Injector, ROUTES
] ]
} }
] ]

View File

@ -26,7 +26,6 @@
"test/url_tree.spec.ts", "test/url_tree.spec.ts",
"test/utils/tree.spec.ts", "test/utils/tree.spec.ts",
"test/url_serializer.spec.ts", "test/url_serializer.spec.ts",
"test/resolve.spec.ts",
"test/apply_redirects.spec.ts", "test/apply_redirects.spec.ts",
"test/recognize.spec.ts", "test/recognize.spec.ts",
"test/create_router_state.spec.ts", "test/create_router_state.spec.ts",

View File

@ -127,7 +127,7 @@ export class DraftsCmp {
} }
export const ROUTER_CONFIG = [ export const ROUTER_CONFIG = [
{path: '', terminal: true, redirectTo: 'inbox'}, {path: 'inbox', component: InboxCmp}, {path: '', pathMatch: 'full', redirectTo: 'inbox'}, {path: 'inbox', component: InboxCmp},
{path: 'drafts', component: DraftsCmp}, {path: 'detail', loadChildren: 'app/inbox-detail.js'} {path: 'drafts', component: DraftsCmp}, {path: 'detail', loadChildren: 'app/inbox-detail.js'}
]; ];

View File

@ -69,7 +69,7 @@ export declare class DefaultUrlSerializer implements UrlSerializer {
/** @stable */ /** @stable */
export declare type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized; export declare type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized;
/** @experimental */ /** @stable */
export interface ExtraOptions { export interface ExtraOptions {
enableTracing?: boolean; enableTracing?: boolean;
useHash?: boolean; useHash?: boolean;
@ -134,13 +134,7 @@ export declare type Params = {
/** @experimental */ /** @experimental */
export declare const PRIMARY_OUTLET: string; export declare const PRIMARY_OUTLET: string;
/** @experimental */ /** @stable */
export declare function provideRouter(config: Routes, opts?: ExtraOptions): any[];
/** @deprecated */
export declare function provideRouterConfig(config: ExtraOptions): any;
/** @deprecated */
export declare function provideRoutes(routes: Routes): any; export declare function provideRoutes(routes: Routes): any;
/** @experimental */ /** @experimental */
@ -160,7 +154,7 @@ export interface Route {
canDeactivate?: any[]; canDeactivate?: any[];
canLoad?: any[]; canLoad?: any[];
children?: Route[]; children?: Route[];
component?: Type<any> | string; component?: Type<any>;
data?: Data; data?: Data;
loadChildren?: LoadChildren; loadChildren?: LoadChildren;
outlet?: string; outlet?: string;
@ -168,7 +162,6 @@ export interface Route {
pathMatch?: string; pathMatch?: string;
redirectTo?: string; redirectTo?: string;
resolve?: ResolveData; resolve?: ResolveData;
/** @deprecated */ terminal?: boolean;
} }
/** @stable */ /** @stable */
@ -178,7 +171,7 @@ export declare class Router {
/** @experimental */ navigated: boolean; /** @experimental */ navigated: boolean;
routerState: RouterState; routerState: RouterState;
url: string; url: string;
constructor(rootComponentType: Type<any>, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); constructor(rootComponentType: Type<any>, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree; createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree;
dispose(): void; dispose(): void;
initialNavigation(): void; initialNavigation(): void;
@ -194,9 +187,6 @@ export declare class Router {
/** @stable */ /** @stable */
export declare const ROUTER_DIRECTIVES: (typeof RouterOutlet | typeof RouterLink | typeof RouterLinkWithHref | typeof RouterLinkActive)[]; export declare const ROUTER_DIRECTIVES: (typeof RouterOutlet | typeof RouterLink | typeof RouterLinkWithHref | typeof RouterLinkActive)[];
/** @deprecated */
export declare type RouterConfig = Route[];
/** @stable */ /** @stable */
export declare class RouterLink { export declare class RouterLink {
fragment: string; fragment: string;
@ -275,16 +265,12 @@ export declare class RouterOutletMap {
/** @stable */ /** @stable */
export declare class RouterState extends Tree<ActivatedRoute> { export declare class RouterState extends Tree<ActivatedRoute> {
/** @deprecated */ fragment: Observable<string>;
/** @deprecated */ queryParams: Observable<Params>;
snapshot: RouterStateSnapshot; snapshot: RouterStateSnapshot;
toString(): string; toString(): string;
} }
/** @stable */ /** @stable */
export declare class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> { export declare class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
/** @deprecated */ fragment: string;
/** @deprecated */ queryParams: Params;
url: string; url: string;
toString(): string; toString(): string;
} }