diff --git a/modules/@angular/router/index.ts b/modules/@angular/router/index.ts index 74c9d8e6da..687da8d39c 100644 --- a/modules/@angular/router/index.ts +++ b/modules/@angular/router/index.ts @@ -7,16 +7,14 @@ */ -export {ExtraOptions, provideRouterConfig, provideRoutes} from './src/common_router_providers'; -export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, RouterConfig, Routes} from './src/config'; +export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, Routes} from './src/config'; export {RouterLink, RouterLinkWithHref} from './src/directives/router_link'; export {RouterLinkActive} from './src/directives/router_link_active'; export {RouterOutlet} from './src/directives/router_outlet'; export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './src/interfaces'; 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 {provideRouter} from './src/router_providers'; export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './src/router_state'; export {PRIMARY_OUTLET, Params} from './src/shared'; export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './src/url_tree'; diff --git a/modules/@angular/router/src/apply_redirects.ts b/modules/@angular/router/src/apply_redirects.ts index 23e6aae07d..ed5e0974bb 100644 --- a/modules/@angular/router/src/apply_redirects.ts +++ b/modules/@angular/router/src/apply_redirects.ts @@ -285,8 +285,7 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment const noMatch = {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; if (route.path === '') { - if ((route.terminal || route.pathMatch === 'full') && - (segmentGroup.hasChildren() || segments.length > 0)) { + if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) { return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; } else { return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; @@ -315,7 +314,8 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment 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: {}}; } @@ -434,8 +434,7 @@ function containsEmptyPathRedirects( function emptyPathRedirect( segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { - if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && - (r.terminal || r.pathMatch === 'full')) + if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') return false; return r.path === '' && r.redirectTo !== undefined; } diff --git a/modules/@angular/router/src/common_router_providers.ts b/modules/@angular/router/src/common_router_providers.ts deleted file mode 100644 index acc19038c1..0000000000 --- a/modules/@angular/router/src/common_router_providers.ts +++ /dev/null @@ -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: ${(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}; -} diff --git a/modules/@angular/router/src/config.ts b/modules/@angular/router/src/config.ts index 210938c5aa..b21f94d8c9 100644 --- a/modules/@angular/router/src/config.ts +++ b/modules/@angular/router/src/config.ts @@ -9,230 +9,6 @@ import {Type} from '@angular/core'; 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: * @@ -491,13 +267,8 @@ export type LoadChildren = string | LoadChildrenCallback; */ export interface Route { path?: string; - - /** - * @deprecated - use `pathMatch` instead - */ - terminal?: boolean; pathMatch?: string; - component?: Type|string; + component?: Type; redirectTo?: string; outlet?: string; canActivate?: any[]; @@ -546,8 +317,7 @@ function validateNode(route: Route): void { throw new Error( `Invalid route configuration of route '${route.path}': path cannot start with a slash`); } - if (route.path === '' && route.redirectTo !== undefined && - (route.terminal === undefined && route.pathMatch === undefined)) { + if (route.path === '' && route.redirectTo !== undefined && route.pathMatch === undefined) { const exp = `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`; throw new Error( diff --git a/modules/@angular/router/src/directives/router_outlet.ts b/modules/@angular/router/src/directives/router_outlet.ts index 9035e375bd..a87de10a53 100644 --- a/modules/@angular/router/src/directives/router_outlet.ts +++ b/modules/@angular/router/src/directives/router_outlet.ts @@ -84,23 +84,10 @@ export class RouterOutlet implements OnDestroy { const component: any = snapshot._routeConfig.component; let factory: ComponentFactory; - try { - if (typeof component === 'string') { - factory = snapshot._resolvedComponentFactory; - } else if (loadedResolver) { - factory = loadedResolver.resolveComponentFactory(component); - } else { - 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; + if (loadedResolver) { + factory = loadedResolver.resolveComponentFactory(component); + } else { + factory = this.resolver.resolveComponentFactory(component); } const injector = loadedInjector ? loadedInjector : this.location.parentInjector; diff --git a/modules/@angular/router/src/recognize.ts b/modules/@angular/router/src/recognize.ts index 3985f518dc..837834ec9f 100644 --- a/modules/@angular/router/src/recognize.ts +++ b/modules/@angular/router/src/recognize.ts @@ -182,8 +182,7 @@ function match( segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[], parent: ActivatedRouteSnapshot) { if (route.path === '') { - if ((route.terminal || route.pathMatch === 'full') && - (segmentGroup.hasChildren() || segments.length > 0)) { + if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) { throw new NoMatch(); } else { const params = parent ? parent.params : {}; @@ -213,7 +212,7 @@ function match( currentIndex++; } - if ((route.terminal || route.pathMatch === 'full') && + if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || currentIndex < segments.length)) { throw new NoMatch(); } @@ -334,8 +333,7 @@ function containsEmptyPathMatches( function emptyPathMatch( segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { - if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && - (r.terminal || r.pathMatch === 'full')) + if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') return false; return r.path === '' && r.redirectTo === undefined; } diff --git a/modules/@angular/router/src/resolve.ts b/modules/@angular/router/src/resolve.ts deleted file mode 100644 index 80cdde8f37..0000000000 --- a/modules/@angular/router/src/resolve.ts +++ /dev/null @@ -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 { - return resolveNode(resolver, state._root).map(_ => state); -} - -function resolveNode( - resolver: ComponentResolver, node: TreeNode): Observable { - if (node.children.length === 0) { - return fromPromise(resolveComponent(resolver, 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, node.value).then(factory => { - node.value._resolvedComponentFactory = factory; - return node.value; - })); - } -} - -function resolveComponent( - resolver: ComponentResolver, snapshot: ActivatedRouteSnapshot): Promise { - if (snapshot.component && snapshot._routeConfig && typeof snapshot.component === 'string') { - return resolver.resolveComponent(snapshot.component); - } else { - return Promise.resolve(null); - } -} \ No newline at end of file diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index f9030549fc..7fcc1a57bd 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce'; import 'rxjs/add/operator/every'; 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 {Subject} from 'rxjs/Subject'; import {Subscription} from 'rxjs/Subscription'; @@ -26,7 +26,6 @@ import {createRouterState} from './create_router_state'; import {createUrlTree} from './create_url_tree'; import {RouterOutlet} from './directives/router_outlet'; import {recognize} from './recognize'; -import {resolve} from './resolve'; import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; import {RouterOutletMap} from './router_outlet_map'; import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state'; @@ -145,10 +144,9 @@ export class Router { * Creates the router service. */ constructor( - private rootComponentType: Type, private resolver: ComponentResolver, - private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, - private location: Location, private injector: Injector, loader: NgModuleFactoryLoader, - compiler: Compiler, public config: Routes) { + private rootComponentType: Type, private urlSerializer: UrlSerializer, + private outletMap: RouterOutletMap, private location: Location, private injector: Injector, + loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) { this.resetConfig(config); this.routerEvents = new Subject(); this.currentUrlTree = createEmptyUrlTree(); @@ -380,10 +378,10 @@ export class Router { this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)); }) - .mergeMap((newRouterStateSnapshot) => { + .map((newRouterStateSnapshot) => { this.routerEvents.next(new RoutesRecognized( id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot)); - return resolve(this.resolver, newRouterStateSnapshot); + return newRouterStateSnapshot; }) .map((routerStateSnapshot) => { diff --git a/modules/@angular/router/src/router_config_loader.ts b/modules/@angular/router/src/router_config_loader.ts index f0e01fe9c0..12e1e34173 100644 --- a/modules/@angular/router/src/router_config_loader.ts +++ b/modules/@angular/router/src/router_config_loader.ts @@ -15,11 +15,6 @@ import {LoadChildren, Route} from './config'; import {flatten, wrapIntoObservable} from './utils/collection'; - -/** - * @deprecated use Routes - */ -export const ROUTER_CONFIG = new OpaqueToken('ROUTER_CONFIG'); export const ROUTES = new OpaqueToken('ROUTES'); export class LoadedRouterConfig { diff --git a/modules/@angular/router/src/router_module.ts b/modules/@angular/router/src/router_module.ts index a667d5f66c..db0093e47b 100644 --- a/modules/@angular/router/src/router_module.ts +++ b/modules/@angular/router/src/router_module.ts @@ -7,10 +7,9 @@ */ 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 {Routes} from './config'; +import {Route, Routes} from './config'; import {RouterLink, RouterLinkWithHref} from './directives/router_link'; import {RouterLinkActive} from './directives/router_link_active'; import {RouterOutlet} from './directives/router_outlet'; @@ -19,6 +18,7 @@ import {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'; @@ -27,6 +27,11 @@ import {DefaultUrlSerializer, UrlSerializer} from './url_tree'; */ export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkWithHref, RouterLinkActive]; +/** + * @stable + */ +export const ROUTER_CONFIGURATION = new OpaqueToken('ROUTER_CONFIGURATION'); + const pathLocationStrategy = { provide: LocationStrategy, useClass: PathLocationStrategy @@ -41,8 +46,8 @@ export const ROUTER_PROVIDERS: any[] = [ provide: Router, useFactory: setupRouter, deps: [ - ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector, - NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION + ApplicationRef, UrlSerializer, RouterOutletMap, Location, Injector, NgModuleFactoryLoader, + Compiler, ROUTES, ROUTER_CONFIGURATION ] }, RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]}, @@ -102,4 +107,64 @@ export function provideLocationStrategy( platformLocationStrategy: PlatformLocation, baseHref: string, options: ExtraOptions = {}) { return options.useHash ? new HashLocationStrategy(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: ${(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] + }; } \ No newline at end of file diff --git a/modules/@angular/router/src/router_providers.ts b/modules/@angular/router/src/router_providers.ts deleted file mode 100644 index 8ba974099b..0000000000 --- a/modules/@angular/router/src/router_providers.ts +++ /dev/null @@ -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) - ]; -} diff --git a/modules/@angular/router/src/router_state.ts b/modules/@angular/router/src/router_state.ts index 0113a0454e..69090fe619 100644 --- a/modules/@angular/router/src/router_state.ts +++ b/modules/@angular/router/src/router_state.ts @@ -43,16 +43,6 @@ export class RouterState extends Tree { setRouterStateSnapshot(this, root); } - /** - * @deprecated (Use root.queryParams) - */ - get queryParams(): Observable { return this.root.queryParams; } - - /** - * @deprecated (Use root.fragment) - */ - get fragment(): Observable { return this.root.fragment; } - toString(): string { return this.snapshot.toString(); } } @@ -174,11 +164,6 @@ export class InheritedResolve { * @stable */ export class ActivatedRouteSnapshot { - /** - * @internal - */ - _resolvedComponentFactory: ComponentFactory; - /** @internal **/ _routeConfig: Route; @@ -251,16 +236,6 @@ export class RouterStateSnapshot extends Tree { setRouterStateSnapshot(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); } } diff --git a/modules/@angular/router/src/utils/tree.ts b/modules/@angular/router/src/utils/tree.ts index e325d3c7ae..7fd12951ff 100644 --- a/modules/@angular/router/src/utils/tree.ts +++ b/modules/@angular/router/src/utils/tree.ts @@ -15,7 +15,7 @@ export class Tree { get root(): T { return this._root.value; } /** - * @deprecated (use ActivatedRoute.parent instead) + * @internal */ parent(t: T): T { const p = this.pathFromRoot(t); @@ -23,7 +23,7 @@ export class Tree { } /** - * @deprecated (use ActivatedRoute.children instead) + * @internal */ children(t: T): T[] { const n = findNode(t, this._root); @@ -31,7 +31,7 @@ export class Tree { } /** - * @deprecated (use ActivatedRoute.firstChild instead) + * @internal */ firstChild(t: T): T { const n = findNode(t, this._root); @@ -39,7 +39,7 @@ export class Tree { } /** - * @deprecated + * @internal */ siblings(t: T): T[] { const p = findPath(t, this._root, []); @@ -50,7 +50,7 @@ export class Tree { } /** - * @deprecated (use ActivatedRoute.pathFromRoot instead) + * @internal */ pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); } } diff --git a/modules/@angular/router/test/apply_redirects.spec.ts b/modules/@angular/router/test/apply_redirects.spec.ts index 3dc1d49c79..ceae5f3cc2 100644 --- a/modules/@angular/router/test/apply_redirects.spec.ts +++ b/modules/@angular/router/test/apply_redirects.spec.ts @@ -414,7 +414,7 @@ describe('applyRedirects', () => { children: [ {path: 'b', component: ComponentB}, {path: '', redirectTo: 'b'}, {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)')); }); diff --git a/modules/@angular/router/test/config.spec.ts b/modules/@angular/router/test/config.spec.ts index 9791164412..f54923fb48 100644 --- a/modules/@angular/router/test/config.spec.ts +++ b/modules/@angular/router/test/config.spec.ts @@ -52,7 +52,7 @@ describe('config', () => { it('should throw when path is missing', () => { expect(() => { - validateConfig([{component: '', redirectTo: 'b'}]); + validateConfig([{component: null, redirectTo: 'b'}]); }).toThrowError(`Invalid route configuration: routes must have path specified`); }); @@ -76,7 +76,7 @@ describe('config', () => { }); it('should throw when pathPatch is invalid', () => { - expect(() => { validateConfig([{path: 'a', pathMatch: 'invalid', component: 'b'}]); }) + expect(() => { validateConfig([{path: 'a', pathMatch: 'invalid', component: ComponentB}]); }) .toThrowError( /Invalid configuration of route 'a': pathMatch can only be set to 'prefix' or 'full'/); }); diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts index 3c6f5eb1e0..7d3b40fd37 100644 --- a/modules/@angular/router/test/integration.spec.ts +++ b/modules/@angular/router/test/integration.spec.ts @@ -10,8 +10,8 @@ import 'rxjs/add/operator/map'; import {Location} from '@angular/common'; import {Component, NgModule, NgModuleFactoryLoader} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing'; -import {TestComponentBuilder, addProviders} from '@angular/core/testing/testing_internal'; +import {ComponentFixture, TestBed, fakeAsync, getTestBed, inject, tick} from '@angular/core/testing'; +import {addProviders} from '@angular/core/testing/testing_internal'; import {expect} from '@angular/platform-browser/testing/matchers'; import {Observable} from 'rxjs/Observable'; import {of } from 'rxjs/observable/of'; @@ -26,561 +26,538 @@ describe('Integration', () => { providers: [provideRoutes( [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])], declarations: [ - BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp, - RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp, - QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf, - ComponentRecordingRoutePathAndUrl, RouteCmp + BlankCmp, + SimpleCmp, + TeamCmp, + UserCmp, + StringLinkCmp, + DummyLinkCmp, + AbsoluteLinkCmp, + RelativeLinkCmp, + DummyLinkWithParentCmp, + LinkWithQueryParamsAndFragment, + CollectParamsCmp, + QueryParamsAndFragmentCmp, + StringLinkButtonCmp, + WrapperCmp, + LinkInNgIf, + ComponentRecordingRoutePathAndUrl, + RouteCmp, + RootCmp, + RelativeLinkInIfCmp, + RootCmpWithTwoOutlets ] }); }); it('should navigate with a provided config', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.navigateByUrl('/simple'); - advance(fixture); + router.navigateByUrl('/simple'); + advance(fixture); - expect(location.path()).toEqual('/simple'); - }))); + expect(location.path()).toEqual('/simple'); + }))); it('should work when an outlet is in an ngIf', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'child', - component: LinkInNgIf, - children: [{path: 'simple', component: SimpleCmp}] - }]); + router.resetConfig([{ + path: 'child', + component: LinkInNgIf, + children: [{path: 'simple', component: SimpleCmp}] + }]); - router.navigateByUrl('/child/simple'); - advance(fixture); + router.navigateByUrl('/child/simple'); + advance(fixture); - 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)', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - @Component({ - selector: 'someRoot', - template: `
`, - entryComponents: [BlankCmp, SimpleCmp] - }) - class RootCmpWithLink { - cond: boolean = true; - } + it('should work when an outlet is in an ngIf (and is removed)', fakeAsync(() => { - const fixture = createRoot(tcb, router, RootCmpWithLink); + @Component({ + selector: 'someRoot', + template: `
`, + entryComponents: [BlankCmp, SimpleCmp] + }) + class RootCmpWithLink { + cond: boolean = true; + } + TestBed.configureTestingModule({declarations: [RootCmpWithLink]}); - router.resetConfig( - [{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]); + const router: Router = getTestBed().get(Router); + const location: Location = getTestBed().get(Location); - router.navigateByUrl('/simple'); - advance(fixture); - expect(location.path()).toEqual('/simple'); + const fixture = createRoot(router, RootCmpWithLink); - const instance = fixture.componentInstance; - instance.cond = false; - advance(fixture); + router.resetConfig( + [{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]); - let recordedError: any = null; - router.navigateByUrl('/blank').catch(e => recordedError = e); - advance(fixture); - expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\''); - }))); + router.navigateByUrl('/simple'); + advance(fixture); + expect(location.path()).toEqual('/simple'); + + const instance = fixture.componentInstance; + instance.cond = false; + advance(fixture); + + let recordedError: any = null; + router.navigateByUrl('/blank').catch(e => recordedError = e); + advance(fixture); + expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\''); + })); it('should update location when navigating', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{path: 'team/:id', component: TeamCmp}]); + router.resetConfig([{path: 'team/:id', component: TeamCmp}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33'); - advance(fixture); + router.navigateByUrl('/team/33'); + advance(fixture); - expect(location.path()).toEqual('/team/33'); - }))); + expect(location.path()).toEqual('/team/33'); + }))); it('should skip location update when using NavigationExtras.skipLocationChange with navigateByUrl', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = tcb.createFakeAsync(RootCmp); - advance(fixture); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = TestBed.createComponent(RootCmp); + advance(fixture); - router.resetConfig([{path: 'team/:id', component: TeamCmp}]); + router.resetConfig([{path: 'team/:id', component: TeamCmp}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ , right: ]'); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ , right: ]'); - router.navigateByUrl('/team/33', {skipLocationChange: true}); - advance(fixture); + router.navigateByUrl('/team/33', {skipLocationChange: true}); + advance(fixture); - expect(location.path()).toEqual('/team/22'); + expect(location.path()).toEqual('/team/22'); - expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ , right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ , right: ]'); + }))); it('should skip location update when using NavigationExtras.skipLocationChange with navigate', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = tcb.createFakeAsync(RootCmp); - advance(fixture); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = TestBed.createComponent(RootCmp); + advance(fixture); - router.resetConfig([{path: 'team/:id', component: TeamCmp}]); + router.resetConfig([{path: 'team/:id', component: TeamCmp}]); - router.navigate(['/team/22']); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigate(['/team/22']); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ , right: ]'); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ , right: ]'); - router.navigate(['/team/33'], {skipLocationChange: true}); - advance(fixture); + router.navigate(['/team/33'], {skipLocationChange: true}); + advance(fixture); - expect(location.path()).toEqual('/team/22'); + expect(location.path()).toEqual('/team/22'); - expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ , right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ , right: ]'); + }))); it('should navigate back and forward', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: + [{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}] + }]); - router.navigateByUrl('/team/33/simple'); - advance(fixture); - expect(location.path()).toEqual('/team/33/simple'); + router.navigateByUrl('/team/33/simple'); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - location.back(); - advance(fixture); - expect(location.path()).toEqual('/team/33/simple'); + location.back(); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); - location.forward(); - advance(fixture); - expect(location.path()).toEqual('/team/22/user/victor'); - }))); + location.forward(); + advance(fixture); + expect(location.path()).toEqual('/team/22/user/victor'); + }))); it('should navigate when locations changes', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [{path: 'user/:name', component: UserCmp}] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [{path: 'user/:name', component: UserCmp}] + }]); - const recordedEvents: any[] = []; - router.events.forEach(e => recordedEvents.push(e)); + const recordedEvents: any[] = []; + router.events.forEach(e => recordedEvents.push(e)); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - (location).simulateHashChange('/team/22/user/fedor'); - advance(fixture); + (location).simulateHashChange('/team/22/user/fedor'); + advance(fixture); - (location).simulateUrlPop('/team/22/user/fedor'); - advance(fixture); + (location).simulateUrlPop('/team/22/user/fedor'); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ user fedor, right: ]'); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ user fedor, right: ]'); - expectEvents(recordedEvents, [ - [NavigationStart, '/team/22/user/victor'], [RoutesRecognized, '/team/22/user/victor'], - [NavigationEnd, '/team/22/user/victor'], + expectEvents(recordedEvents, [ + [NavigationStart, '/team/22/user/victor'], [RoutesRecognized, '/team/22/user/victor'], + [NavigationEnd, '/team/22/user/victor'], - [NavigationStart, '/team/22/user/fedor'], [RoutesRecognized, '/team/22/user/fedor'], - [NavigationEnd, '/team/22/user/fedor'] - ]); - }))); + [NavigationStart, '/team/22/user/fedor'], [RoutesRecognized, '/team/22/user/fedor'], + [NavigationEnd, '/team/22/user/fedor'] + ]); + }))); it('should update the location when the matched route does not change', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{path: '**', component: CollectParamsCmp}]); + router.resetConfig([{path: '**', component: CollectParamsCmp}]); - router.navigateByUrl('/one/two'); - advance(fixture); - const cmp = fixture.debugElement.children[1].componentInstance; - expect(location.path()).toEqual('/one/two'); - expect(fixture.debugElement.nativeElement).toHaveText('collect-params'); + router.navigateByUrl('/one/two'); + advance(fixture); + const cmp = fixture.debugElement.children[1].componentInstance; + expect(location.path()).toEqual('/one/two'); + expect(fixture.debugElement.nativeElement).toHaveText('collect-params'); - expect(cmp.recordedUrls()).toEqual(['one/two']); + expect(cmp.recordedUrls()).toEqual(['one/two']); - router.navigateByUrl('/three/four'); - advance(fixture); - expect(location.path()).toEqual('/three/four'); - expect(fixture.debugElement.nativeElement).toHaveText('collect-params'); - expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']); - }))); + router.navigateByUrl('/three/four'); + advance(fixture); + expect(location.path()).toEqual('/three/four'); + expect(fixture.debugElement.nativeElement).toHaveText('collect-params'); + expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']); + }))); - it('should support secondary routes', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should support secondary routes', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'user/:name', component: UserCmp}, - {path: 'simple', component: SimpleCmp, outlet: 'right'} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'user/:name', component: UserCmp}, + {path: 'simple', component: SimpleCmp, outlet: 'right'} + ] + }]); - router.navigateByUrl('/team/22/(user/victor//right:simple)'); - advance(fixture); + router.navigateByUrl('/team/22/(user/victor//right:simple)'); + advance(fixture); - expect(fixture.debugElement.nativeElement) - .toHaveText('team 22 [ user victor, right: simple ]'); - }))); + expect(fixture.debugElement.nativeElement) + .toHaveText('team 22 [ user victor, right: simple ]'); + }))); - it('should deactivate outlets', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should deactivate outlets', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'user/:name', component: UserCmp}, - {path: 'simple', component: SimpleCmp, outlet: 'right'} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'user/:name', component: UserCmp}, + {path: 'simple', component: SimpleCmp, outlet: 'right'} + ] + }]); - router.navigateByUrl('/team/22/(user/victor//right:simple)'); - advance(fixture); + router.navigateByUrl('/team/22/(user/victor//right:simple)'); + advance(fixture); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - expect(fixture.debugElement.nativeElement) - .toHaveText('team 22 [ user victor, right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ user victor, right: ]'); + }))); - it('should deactivate nested outlets', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should deactivate nested outlets', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([ - { - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'user/:name', component: UserCmp}, - {path: 'simple', component: SimpleCmp, outlet: 'right'} - ] - }, - {path: '', component: BlankCmp} - ]); + router.resetConfig([ + { + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'user/:name', component: UserCmp}, + {path: 'simple', component: SimpleCmp, outlet: 'right'} + ] + }, + {path: '', component: BlankCmp} + ]); - router.navigateByUrl('/team/22/(user/victor//right:simple)'); - advance(fixture); + router.navigateByUrl('/team/22/(user/victor//right:simple)'); + advance(fixture); - router.navigateByUrl('/'); - advance(fixture); + router.navigateByUrl('/'); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText(''); - }))); + expect(fixture.debugElement.nativeElement).toHaveText(''); + }))); - it('should set query params and fragment', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should set query params and fragment', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{path: 'query', component: QueryParamsAndFragmentCmp}]); + router.resetConfig([{path: 'query', component: QueryParamsAndFragmentCmp}]); - router.navigateByUrl('/query?name=1#fragment1'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('query: 1 fragment: fragment1'); + router.navigateByUrl('/query?name=1#fragment1'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('query: 1 fragment: fragment1'); - router.navigateByUrl('/query?name=2#fragment2'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2'); - }))); + router.navigateByUrl('/query?name=2#fragment2'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2'); + }))); - it('should push params only when they change', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should push params only when they change', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [{path: 'user/:name', component: UserCmp}] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [{path: 'user/:name', component: UserCmp}] + }]); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); - const team = fixture.debugElement.children[1].componentInstance; - const user = fixture.debugElement.children[1].children[1].componentInstance; + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); + const team = fixture.debugElement.children[1].componentInstance; + const user = fixture.debugElement.children[1].children[1].componentInstance; - expect(team.recordedParams).toEqual([{id: '22'}]); - expect(user.recordedParams).toEqual([{name: 'victor'}]); + expect(team.recordedParams).toEqual([{id: '22'}]); + expect(user.recordedParams).toEqual([{name: 'victor'}]); - router.navigateByUrl('/team/22/user/fedor'); - advance(fixture); + router.navigateByUrl('/team/22/user/fedor'); + advance(fixture); - expect(team.recordedParams).toEqual([{id: '22'}]); - expect(user.recordedParams).toEqual([{name: 'victor'}, {name: 'fedor'}]); - }))); + expect(team.recordedParams).toEqual([{id: '22'}]); + expect(user.recordedParams).toEqual([{name: 'victor'}, {name: 'fedor'}]); + }))); - it('should work when navigating to /', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should work when navigating to /', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([ - {path: '', pathMatch: 'full', component: SimpleCmp}, - {path: 'user/:name', component: UserCmp} - ]); + router.resetConfig([ + {path: '', pathMatch: 'full', component: SimpleCmp}, + {path: 'user/:name', component: UserCmp} + ]); - router.navigateByUrl('/user/victor'); - advance(fixture); + router.navigateByUrl('/user/victor'); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('user victor'); + expect(fixture.debugElement.nativeElement).toHaveText('user victor'); - router.navigateByUrl('/'); - advance(fixture); + router.navigateByUrl('/'); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('simple'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('simple'); + }))); - it('should cancel in-flight navigations', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should cancel in-flight navigations', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{path: 'user/:name', component: UserCmp}]); + router.resetConfig([{path: 'user/:name', component: UserCmp}]); - const recordedEvents: any[] = []; - router.events.forEach(e => recordedEvents.push(e)); + const recordedEvents: any[] = []; + router.events.forEach(e => recordedEvents.push(e)); - router.navigateByUrl('/user/init'); - advance(fixture); + router.navigateByUrl('/user/init'); + advance(fixture); - const user = fixture.debugElement.children[1].componentInstance; + const user = fixture.debugElement.children[1].componentInstance; - let r1: any, r2: any; - router.navigateByUrl('/user/victor').then(_ => r1 = _); - router.navigateByUrl('/user/fedor').then(_ => r2 = _); - advance(fixture); + let r1: any, r2: any; + router.navigateByUrl('/user/victor').then(_ => r1 = _); + router.navigateByUrl('/user/fedor').then(_ => r2 = _); + advance(fixture); - expect(r1).toEqual(false); // returns false because it was canceled - expect(r2).toEqual(true); // returns true because it was successful + expect(r1).toEqual(false); // returns false because it was canceled + expect(r2).toEqual(true); // returns true because it was successful - expect(fixture.debugElement.nativeElement).toHaveText('user fedor'); - expect(user.recordedParams).toEqual([{name: 'init'}, {name: 'fedor'}]); + expect(fixture.debugElement.nativeElement).toHaveText('user fedor'); + expect(user.recordedParams).toEqual([{name: 'init'}, {name: 'fedor'}]); - expectEvents(recordedEvents, [ - [NavigationStart, '/user/init'], [RoutesRecognized, '/user/init'], - [NavigationEnd, '/user/init'], + expectEvents(recordedEvents, [ + [NavigationStart, '/user/init'], [RoutesRecognized, '/user/init'], + [NavigationEnd, '/user/init'], - [NavigationStart, '/user/victor'], [NavigationStart, '/user/fedor'], + [NavigationStart, '/user/victor'], [NavigationStart, '/user/fedor'], - [NavigationCancel, '/user/victor'], [RoutesRecognized, '/user/fedor'], - [NavigationEnd, '/user/fedor'] - ]); - }))); + [NavigationCancel, '/user/victor'], [RoutesRecognized, '/user/fedor'], + [NavigationEnd, '/user/fedor'] + ]); + }))); - it('should handle failed navigations gracefully', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should handle failed navigations gracefully', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{path: 'user/:name', component: UserCmp}]); + router.resetConfig([{path: 'user/:name', component: UserCmp}]); - const recordedEvents: any[] = []; - router.events.forEach(e => recordedEvents.push(e)); + const recordedEvents: any[] = []; + router.events.forEach(e => recordedEvents.push(e)); - let e: any; - router.navigateByUrl('/invalid').catch(_ => e = _); - advance(fixture); - expect(e.message).toContain('Cannot match any routes'); + let e: any; + router.navigateByUrl('/invalid').catch(_ => e = _); + advance(fixture); + expect(e.message).toContain('Cannot match any routes'); - router.navigateByUrl('/user/fedor'); - advance(fixture); + router.navigateByUrl('/user/fedor'); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('user fedor'); + expect(fixture.debugElement.nativeElement).toHaveText('user fedor'); - expectEvents(recordedEvents, [ - [NavigationStart, '/invalid'], [NavigationError, '/invalid'], + expectEvents(recordedEvents, [ + [NavigationStart, '/invalid'], [NavigationError, '/invalid'], - [NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'], - [NavigationEnd, '/user/fedor'] - ]); - }))); + [NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'], + [NavigationEnd, '/user/fedor'] + ]); + }))); it('should replace state when path is equal to current path', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: + [{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}] + }]); - router.navigateByUrl('/team/33/simple'); - advance(fixture); + router.navigateByUrl('/team/33/simple'); + advance(fixture); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - location.back(); - advance(fixture); - expect(location.path()).toEqual('/team/33/simple'); - }))); + location.back(); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); + }))); it('should handle componentless paths', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmpWithTwoOutlets); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmpWithTwoOutlets); - router.resetConfig([ - { - path: 'parent/:id', - children: [ - {path: 'simple', component: SimpleCmp}, - {path: 'user/:name', component: UserCmp, outlet: 'right'} - ] - }, - {path: 'user/:name', component: UserCmp} - ]); + router.resetConfig([ + { + path: 'parent/:id', + children: [ + {path: 'simple', component: SimpleCmp}, + {path: 'user/:name', component: UserCmp, outlet: 'right'} + ] + }, + {path: 'user/:name', component: UserCmp} + ]); - // navigate to a componentless route - router.navigateByUrl('/parent/11/(simple//right:user/victor)'); - advance(fixture); - expect(location.path()).toEqual('/parent/11/(simple//right:user/victor)'); - expect(fixture.debugElement.nativeElement) - .toHaveText('primary [simple] right [user victor]'); + // navigate to a componentless route + router.navigateByUrl('/parent/11/(simple//right:user/victor)'); + advance(fixture); + expect(location.path()).toEqual('/parent/11/(simple//right:user/victor)'); + expect(fixture.debugElement.nativeElement) + .toHaveText('primary [simple] right [user victor]'); - // navigate to the same route with different params (reuse) - router.navigateByUrl('/parent/22/(simple//right:user/fedor)'); - advance(fixture); - expect(location.path()).toEqual('/parent/22/(simple//right:user/fedor)'); - expect(fixture.debugElement.nativeElement) - .toHaveText('primary [simple] right [user fedor]'); + // navigate to the same route with different params (reuse) + router.navigateByUrl('/parent/22/(simple//right:user/fedor)'); + advance(fixture); + expect(location.path()).toEqual('/parent/22/(simple//right:user/fedor)'); + expect(fixture.debugElement.nativeElement).toHaveText('primary [simple] right [user fedor]'); - // navigate to a normal route (check deactivation) - router.navigateByUrl('/user/victor'); - advance(fixture); - expect(location.path()).toEqual('/user/victor'); - expect(fixture.debugElement.nativeElement).toHaveText('primary [user victor] right []'); + // navigate to a normal route (check deactivation) + router.navigateByUrl('/user/victor'); + advance(fixture); + expect(location.path()).toEqual('/user/victor'); + expect(fixture.debugElement.nativeElement).toHaveText('primary [user victor] right []'); - // navigate back to a componentless route - router.navigateByUrl('/parent/11/(simple//right:user/victor)'); - advance(fixture); - expect(location.path()).toEqual('/parent/11/(simple//right:user/victor)'); - expect(fixture.debugElement.nativeElement) - .toHaveText('primary [simple] right [user victor]'); - }))); + // navigate back to a componentless route + router.navigateByUrl('/parent/11/(simple//right:user/victor)'); + advance(fixture); + expect(location.path()).toEqual('/parent/11/(simple//right:user/victor)'); + expect(fixture.debugElement.nativeElement) + .toHaveText('primary [simple] right [user victor]'); + }))); - it('should emit an event when an outlet gets activated', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - @Component({ - selector: 'container', - template: - `` - }) - class Container { - activations: any[] = []; - deactivations: any[] = []; + it('should emit an event when an outlet gets activated', fakeAsync(() => { + @Component({ + selector: 'container', + template: + `` + }) + class Container { + activations: any[] = []; + deactivations: any[] = []; - recordActivate(component: any): void { this.activations.push(component); } + recordActivate(component: any): void { this.activations.push(component); } - recordDeactivate(component: any): void { this.deactivations.push(component); } - } + recordDeactivate(component: any): void { this.deactivations.push(component); } + } - const fixture = createRoot(tcb, router, Container); - const cmp = fixture.debugElement.componentInstance; + TestBed.configureTestingModule({declarations: [Container]}); - router.resetConfig( - [{path: 'blank', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]); + const router: Router = getTestBed().get(Router); - cmp.activations = []; - cmp.deactivations = []; + const fixture = createRoot(router, Container); + const cmp = fixture.debugElement.componentInstance; - router.navigateByUrl('/blank'); - advance(fixture); + router.resetConfig( + [{path: 'blank', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]); - expect(cmp.activations.length).toEqual(1); - expect(cmp.activations[0] instanceof BlankCmp).toBe(true); + cmp.activations = []; + cmp.deactivations = []; - router.navigateByUrl('/simple'); - advance(fixture); + router.navigateByUrl('/blank'); + advance(fixture); - expect(cmp.activations.length).toEqual(2); - expect(cmp.activations[1] instanceof SimpleCmp).toBe(true); - expect(cmp.deactivations.length).toEqual(2); - expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true); - }))); + expect(cmp.activations.length).toEqual(1); + expect(cmp.activations[0] instanceof BlankCmp).toBe(true); + + router.navigateByUrl('/simple'); + advance(fixture); + + expect(cmp.activations.length).toEqual(2); + expect(cmp.activations[1] instanceof SimpleCmp).toBe(true); + expect(cmp.deactivations.length).toEqual(2); + expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true); + })); it('should update url and router state before activating components', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { + fakeAsync(inject([Router, Location], (router: Router, 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}]); - router.navigateByUrl('/cmp'); - advance(fixture); + router.navigateByUrl('/cmp'); + advance(fixture); - const cmp = fixture.debugElement.children[1].componentInstance; + const cmp = fixture.debugElement.children[1].componentInstance; - expect(cmp.url).toBe('/cmp'); - expect(cmp.path.length).toEqual(2); - }))); + expect(cmp.url).toBe('/cmp'); + expect(cmp.path.length).toEqual(2); + }))); describe('data', () => { class ResolveSix implements Resolve { @@ -596,105 +573,99 @@ describe('Integration', () => { }); it('should provide resolved data', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmpWithTwoOutlets); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmpWithTwoOutlets); - router.resetConfig([{ - path: 'parent/:id', - data: {one: 1}, - resolve: {two: 'resolveTwo'}, - children: [ - {path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp}, - { - path: '', - data: {five: 5}, - resolve: {six: 'resolveSix'}, - component: RouteCmp, - outlet: 'right' - } - ] - }]); + router.resetConfig([{ + path: 'parent/:id', + data: {one: 1}, + resolve: {two: 'resolveTwo'}, + children: [ + {path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp}, { + path: '', + data: {five: 5}, + resolve: {six: 'resolveSix'}, + component: RouteCmp, + outlet: 'right' + } + ] + }]); - router.navigateByUrl('/parent/1'); - advance(fixture); + router.navigateByUrl('/parent/1'); + advance(fixture); - const primaryCmp = fixture.debugElement.children[1].componentInstance; - const rightCmp = fixture.debugElement.children[3].componentInstance; + const primaryCmp = fixture.debugElement.children[1].componentInstance; + const rightCmp = fixture.debugElement.children[3].componentInstance; - expect(primaryCmp.route.snapshot.data).toEqual({one: 1, two: 2, three: 3, four: 4}); - expect(rightCmp.route.snapshot.data).toEqual({one: 1, two: 2, five: 5, six: 6}); + expect(primaryCmp.route.snapshot.data).toEqual({one: 1, two: 2, three: 3, four: 4}); + expect(rightCmp.route.snapshot.data).toEqual({one: 1, two: 2, five: 5, six: 6}); - let primaryRecorded: any[] = []; - primaryCmp.route.data.forEach((rec: any) => primaryRecorded.push(rec)); + let primaryRecorded: any[] = []; + primaryCmp.route.data.forEach((rec: any) => primaryRecorded.push(rec)); - let rightRecorded: any[] = []; - rightCmp.route.data.forEach((rec: any) => rightRecorded.push(rec)); + let rightRecorded: any[] = []; + rightCmp.route.data.forEach((rec: any) => rightRecorded.push(rec)); - router.navigateByUrl('/parent/2'); - advance(fixture); + router.navigateByUrl('/parent/2'); + advance(fixture); - expect(primaryRecorded).toEqual([ - {one: 1, three: 3, two: 2, four: 4}, {one: 1, three: 3, two: 2, four: 4} - ]); - expect(rightRecorded).toEqual([ - {one: 1, five: 5, two: 2, six: 6}, {one: 1, five: 5, two: 2, six: 6} - ]); - }))); + expect(primaryRecorded).toEqual([ + {one: 1, three: 3, two: 2, four: 4}, {one: 1, three: 3, two: 2, four: 4} + ]); + expect(rightRecorded).toEqual([ + {one: 1, five: 5, two: 2, six: 6}, {one: 1, five: 5, two: 2, six: 6} + ]); + }))); }); describe('router links', () => { - it('should support string router links', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should support string router links', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'link', component: StringLinkCmp}, - {path: 'simple', component: SimpleCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: StringLinkCmp}, {path: 'simple', component: SimpleCmp} + ] + }]); - router.navigateByUrl('/team/22/link'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); - const native = fixture.debugElement.nativeElement.querySelector('a'); - expect(native.getAttribute('href')).toEqual('/team/33/simple'); - native.click(); - advance(fixture); + const native = fixture.debugElement.nativeElement.querySelector('a'); + expect(native.getAttribute('href')).toEqual('/team/33/simple'); + native.click(); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); + }))); - it('should not preserve query params and fragment by default', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - @Component({ - selector: 'someRoot', - template: `Link`, - directives: ROUTER_DIRECTIVES - }) - class RootCmpWithLink { - } + it('should not preserve query params and fragment by default', fakeAsync(() => { + @Component({ + selector: 'someRoot', + template: `Link`, + directives: ROUTER_DIRECTIVES + }) + class RootCmpWithLink { + } - const fixture = createRoot(tcb, router, RootCmpWithLink); + TestBed.configureTestingModule({declarations: [RootCmpWithLink]}); + const router: Router = getTestBed().get(Router); - router.resetConfig([{path: 'home', component: SimpleCmp}]); + const fixture = createRoot(router, RootCmpWithLink); - const native = fixture.debugElement.nativeElement.querySelector('a'); + router.resetConfig([{path: 'home', component: SimpleCmp}]); - router.navigateByUrl('/home?q=123#fragment'); - advance(fixture); - expect(native.getAttribute('href')).toEqual('/home'); - }))); + const native = fixture.debugElement.nativeElement.querySelector('a'); - it('should update hrefs when query params or fragment change', - fakeAsync(inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { + router.navigateByUrl('/home?q=123#fragment'); + advance(fixture); + expect(native.getAttribute('href')).toEqual('/home'); + })); + + it('should update hrefs when query params or fragment change', fakeAsync(() => { @Component({ selector: 'someRoot', @@ -704,8 +675,9 @@ describe('Integration', () => { }) 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}]); @@ -722,193 +694,175 @@ describe('Integration', () => { router.navigateByUrl('/home?q=456#1'); advance(fixture); expect(native.getAttribute('href')).toEqual('/home?q=456#1'); + })); + + it('should support using links on non-a tags', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); + + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: StringLinkButtonCmp}, + {path: 'simple', component: SimpleCmp} + ] + }]); + + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); + + const native = fixture.debugElement.nativeElement.querySelector('button'); + native.click(); + advance(fixture); + + expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); }))); - it('should support using links on non-a tags', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should support absolute router links', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'link', component: StringLinkButtonCmp}, - {path: 'simple', component: SimpleCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: AbsoluteLinkCmp}, {path: 'simple', component: SimpleCmp} + ] + }]); - router.navigateByUrl('/team/22/link'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); - const native = fixture.debugElement.nativeElement.querySelector('button'); - native.click(); - advance(fixture); + const native = fixture.debugElement.nativeElement.querySelector('a'); + expect(native.getAttribute('href')).toEqual('/team/33/simple'); + native.click(); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); + }))); - it('should support absolute router links', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should support relative router links', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'link', component: AbsoluteLinkCmp}, - {path: 'simple', component: SimpleCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: RelativeLinkCmp}, {path: 'simple', component: SimpleCmp} + ] + }]); - router.navigateByUrl('/team/22/link'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); - const native = fixture.debugElement.nativeElement.querySelector('a'); - expect(native.getAttribute('href')).toEqual('/team/33/simple'); - native.click(); - advance(fixture); + const native = fixture.debugElement.nativeElement.querySelector('a'); + expect(native.getAttribute('href')).toEqual('/team/22/simple'); + native.click(); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 33 [ simple, right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]'); + }))); - it('should support relative router links', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should support top-level link', fakeAsync(inject([Router], (router: Router) => { + const fixture = createRoot(router, RelativeLinkInIfCmp); + advance(fixture); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'link', component: RelativeLinkCmp}, - {path: 'simple', component: SimpleCmp} - ] - }]); + router.resetConfig( + [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}]); - router.navigateByUrl('/team/22/link'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ link, right: ]'); + router.navigateByUrl('/'); + advance(fixture); + expect(fixture.debugElement.nativeElement).toHaveText(' '); + const cmp = fixture.debugElement.componentInstance; - const native = fixture.debugElement.nativeElement.querySelector('a'); - expect(native.getAttribute('href')).toEqual('/team/22/simple'); - native.click(); - advance(fixture); + cmp.show = true; + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('link '); + const native = fixture.debugElement.nativeElement.querySelector('a'); - it('should support top-level link', - fakeAsync( - inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RelativeLinkInIfCmp); - advance(fixture); + expect(native.getAttribute('href')).toEqual('/simple'); + native.click(); + advance(fixture); - router.resetConfig( - [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}]); - - router.navigateByUrl('/'); - advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText(' '); - const cmp = fixture.debugElement.componentInstance; - - cmp.show = true; - advance(fixture); - - expect(fixture.debugElement.nativeElement).toHaveText('link '); - const native = fixture.debugElement.nativeElement.querySelector('a'); - - expect(native.getAttribute('href')).toEqual('/simple'); - native.click(); - advance(fixture); - - expect(fixture.debugElement.nativeElement).toHaveText('link simple'); - }))); + expect(fixture.debugElement.nativeElement).toHaveText('link simple'); + }))); it('should support query params and fragments', - fakeAsync(inject( - [Router, Location, TestComponentBuilder], - (router: Router, location: Location, tcb: TestComponentBuilder) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: 'link', component: LinkWithQueryParamsAndFragment}, - {path: 'simple', component: SimpleCmp} - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: LinkWithQueryParamsAndFragment}, + {path: 'simple', component: SimpleCmp} + ] + }]); - router.navigateByUrl('/team/22/link'); - advance(fixture); + router.navigateByUrl('/team/22/link'); + advance(fixture); - const native = fixture.debugElement.nativeElement.querySelector('a'); - expect(native.getAttribute('href')).toEqual('/team/22/simple?q=1#f'); - native.click(); - advance(fixture); + const native = fixture.debugElement.nativeElement.querySelector('a'); + expect(native.getAttribute('href')).toEqual('/team/22/simple?q=1#f'); + native.click(); + advance(fixture); - expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]'); + expect(fixture.debugElement.nativeElement).toHaveText('team 22 [ simple, right: ]'); - expect(location.path()).toEqual('/team/22/simple?q=1#f'); - }))); + expect(location.path()).toEqual('/team/22/simple?q=1#f'); + }))); }); describe('redirects', () => { - it('should work', fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('should work', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([ - {path: 'old/team/:id', redirectTo: 'team/:id'}, - {path: 'team/:id', component: TeamCmp} - ]); + router.resetConfig([ + {path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp} + ]); - router.navigateByUrl('old/team/22'); - advance(fixture); + router.navigateByUrl('old/team/22'); + advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + expect(location.path()).toEqual('/team/22'); + }))); it('should not break the back button when trigger by location change', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = tcb.createFakeAsync(RootCmp); - advance(fixture); - router.resetConfig([ - {path: 'initial', component: BlankCmp}, - {path: 'old/team/:id', redirectTo: 'team/:id'}, - {path: 'team/:id', component: TeamCmp} - ]); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = TestBed.createComponent(RootCmp); + advance(fixture); + router.resetConfig([ + {path: 'initial', component: BlankCmp}, {path: 'old/team/:id', redirectTo: 'team/:id'}, + {path: 'team/:id', component: TeamCmp} + ]); - location.go('initial'); - location.go('old/team/22'); + location.go('initial'); + location.go('old/team/22'); - // initial navigation - router.initialNavigation(); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + // initial navigation + router.initialNavigation(); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - location.back(); - advance(fixture); - expect(location.path()).toEqual('/initial'); + location.back(); + advance(fixture); + expect(location.path()).toEqual('/initial'); - // location change - (location).go('/old/team/33'); + // location change + (location).go('/old/team/33'); - advance(fixture); - expect(location.path()).toEqual('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/33'); - location.back(); - advance(fixture); - expect(location.path()).toEqual('/initial'); - }))); + location.back(); + advance(fixture); + expect(location.path()).toEqual('/initial'); + }))); // should not break the back button when trigger by initial navigation }); @@ -920,20 +874,17 @@ describe('Integration', () => { addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]); }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysFalse']}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysFalse']}]); - router.navigateByUrl('/team/22'); - advance(fixture); + router.navigateByUrl('/team/22'); + advance(fixture); - expect(location.path()).toEqual('/'); - }))); + expect(location.path()).toEqual('/'); + }))); }); describe( @@ -943,22 +894,21 @@ describe('Integration', () => { addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]); }); - it('works', fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'parent', - canActivate: ['alwaysFalse'], - children: [{path: 'team/:id', component: TeamCmp}] - }]); + router.resetConfig([{ + path: 'parent', + canActivate: ['alwaysFalse'], + children: [{path: 'team/:id', component: TeamCmp}] + }]); - router.navigateByUrl('parent/team/22'); - advance(fixture); + router.navigateByUrl('parent/team/22'); + advance(fixture); - expect(location.path()).toEqual('/'); - }))); + expect(location.path()).toEqual('/'); + }))); }); describe('should activate a route when CanActivate returns true', () => { @@ -969,20 +919,17 @@ describe('Integration', () => { }]); }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]); - router.navigateByUrl('/team/22'); - advance(fixture); + router.navigateByUrl('/team/22'); + advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + expect(location.path()).toEqual('/team/22'); + }))); }); describe('should work when given a class', () => { @@ -994,19 +941,17 @@ describe('Integration', () => { beforeEach(() => { addProviders([AlwaysTrue]); }); - it('works', fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]); - router.navigateByUrl('/team/22'); - advance(fixture); + router.navigateByUrl('/team/22'); + advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + expect(location.path()).toEqual('/team/22'); + }))); }); describe('should work when returns an observable', () => { @@ -1018,19 +963,16 @@ describe('Integration', () => { }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/'); - }))); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/'); + }))); }); describe('should work when returns a promise', () => { @@ -1048,23 +990,20 @@ describe('Integration', () => { }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); + }))); }); }); @@ -1093,92 +1032,81 @@ describe('Integration', () => { ]); }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([ - {path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivateTeam']} - ]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivateTeam']}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - let successStatus: boolean; - router.navigateByUrl('/team/33').then(res => successStatus = res); - advance(fixture); - expect(location.path()).toEqual('/team/33'); - expect(successStatus).toEqual(true); + let successStatus: boolean; + router.navigateByUrl('/team/33').then(res => successStatus = res); + advance(fixture); + expect(location.path()).toEqual('/team/33'); + expect(successStatus).toEqual(true); - let canceledStatus: boolean; - router.navigateByUrl('/team/44').then(res => canceledStatus = res); - advance(fixture); - expect(location.path()).toEqual('/team/33'); - expect(canceledStatus).toEqual(false); - }))); + let canceledStatus: boolean; + router.navigateByUrl('/team/44').then(res => canceledStatus = res); + advance(fixture); + expect(location.path()).toEqual('/team/33'); + expect(canceledStatus).toEqual(false); + }))); it('works (componentless route)', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'parent/:id', - canDeactivate: ['CanDeactivateParent'], - children: [{path: 'simple', component: SimpleCmp}] - }]); + router.resetConfig([{ + path: 'parent/:id', + canDeactivate: ['CanDeactivateParent'], + children: [{path: 'simple', component: SimpleCmp}] + }]); - router.navigateByUrl('/parent/22/simple'); - advance(fixture); - expect(location.path()).toEqual('/parent/22/simple'); + router.navigateByUrl('/parent/22/simple'); + advance(fixture); + expect(location.path()).toEqual('/parent/22/simple'); - router.navigateByUrl('/parent/33/simple'); - advance(fixture); - expect(location.path()).toEqual('/parent/33/simple'); + router.navigateByUrl('/parent/33/simple'); + advance(fixture); + expect(location.path()).toEqual('/parent/33/simple'); - router.navigateByUrl('/parent/44/simple'); - advance(fixture); - expect(location.path()).toEqual('/parent/33/simple'); - }))); + router.navigateByUrl('/parent/44/simple'); + advance(fixture); + expect(location.path()).toEqual('/parent/33/simple'); + }))); it('works with a nested route', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [ - {path: '', pathMatch: 'full', component: SimpleCmp}, { - path: 'user/:name', - component: UserCmp, - canDeactivate: ['CanDeactivateUser'] - } - ] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: '', pathMatch: 'full', component: SimpleCmp}, + {path: 'user/:name', component: UserCmp, canDeactivate: ['CanDeactivateUser']} + ] + }]); - router.navigateByUrl('/team/22/user/victor'); - advance(fixture); + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); - // this works because we can deactivate victor - router.navigateByUrl('/team/33'); - advance(fixture); - expect(location.path()).toEqual('/team/33'); + // this works because we can deactivate victor + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/33'); - router.navigateByUrl('/team/33/user/fedor'); - advance(fixture); + router.navigateByUrl('/team/33/user/fedor'); + advance(fixture); - // this doesn't work cause we cannot deactivate fedor - router.navigateByUrl('/team/44'); - advance(fixture); - expect(location.path()).toEqual('/team/33/user/fedor'); - }))); + // this doesn't work cause we cannot deactivate fedor + router.navigateByUrl('/team/44'); + advance(fixture); + expect(location.path()).toEqual('/team/33/user/fedor'); + }))); }); describe('should work when given a class', () => { @@ -1192,23 +1120,20 @@ describe('Integration', () => { beforeEach(() => { addProviders([AlwaysTrue]); }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue]}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue]}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33'); - advance(fixture); - expect(location.path()).toEqual('/team/33'); - }))); + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/33'); + }))); }); @@ -1222,23 +1147,20 @@ describe('Integration', () => { }]); }); - it('works', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig( - [{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]); + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]); - router.navigateByUrl('/team/22'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33'); - advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); + }))); }); }); @@ -1251,27 +1173,25 @@ describe('Integration', () => { }]); }); - it('works', fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: '', - canActivateChild: ['alwaysFalse'], - children: [{path: 'team/:id', component: TeamCmp}] - }]); + router.resetConfig([{ + path: '', + canActivateChild: ['alwaysFalse'], + children: [{path: 'team/:id', component: TeamCmp}] + }]); - router.navigateByUrl('/team/22'); - advance(fixture); + router.navigateByUrl('/team/22'); + advance(fixture); - expect(location.path()).toEqual('/team/22'); + expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33').catch(() => {}); - advance(fixture); + router.navigateByUrl('/team/33').catch(() => {}); + advance(fixture); - expect(location.path()).toEqual('/team/22'); - }))); + expect(location.path()).toEqual('/team/22'); + }))); }); }); @@ -1286,9 +1206,8 @@ describe('Integration', () => { it('works', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location, - loader: SpyNgModuleFactoryLoader) => { + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { @Component({selector: 'lazy', template: 'lazy-loaded'}) class LazyLoadedComponent { @@ -1304,7 +1223,7 @@ describe('Integration', () => { } loader.stubbedModules = {lazyFalse: LoadedModule, lazyTrue: LoadedModule}; - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([ {path: 'lazyFalse', canLoad: ['alwaysFalse'], loadChildren: 'lazyFalse'}, @@ -1345,129 +1264,122 @@ describe('Integration', () => { describe('routerActiveLink', () => { it('should set the class when the link is active (a tag)', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [{ - path: 'link', - component: DummyLinkCmp, - children: - [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] - }] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [{ + path: 'link', + component: DummyLinkCmp, + children: + [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] + }] + }]); - router.navigateByUrl('/team/22/link;exact=true'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link;exact=true'); + router.navigateByUrl('/team/22/link;exact=true'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link;exact=true'); - const nativeLink = fixture.debugElement.nativeElement.querySelector('a'); - const nativeButton = fixture.debugElement.nativeElement.querySelector('button'); - expect(nativeLink.className).toEqual('active'); - expect(nativeButton.className).toEqual('active'); + const nativeLink = fixture.debugElement.nativeElement.querySelector('a'); + const nativeButton = fixture.debugElement.nativeElement.querySelector('button'); + expect(nativeLink.className).toEqual('active'); + expect(nativeButton.className).toEqual('active'); - router.navigateByUrl('/team/22/link/simple'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link/simple'); - expect(nativeLink.className).toEqual(''); - expect(nativeButton.className).toEqual(''); - }))); + router.navigateByUrl('/team/22/link/simple'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link/simple'); + expect(nativeLink.className).toEqual(''); + expect(nativeButton.className).toEqual(''); + }))); - it('should not set the class until the first navigation succeeds', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - @Component({ - template: - '' - }) - class RootCmpWithLink { - } + it('should not set the class until the first navigation succeeds', fakeAsync(() => { + @Component({ + template: + '' + }) + class RootCmpWithLink { + } - const f = tcb.createFakeAsync(RootCmpWithLink); - advance(f); + TestBed.configureTestingModule({declarations: [RootCmpWithLink]}); + const router: Router = getTestBed().get(Router); - const link = f.debugElement.nativeElement.querySelector('a'); - expect(link.className).toEqual(''); + const f = TestBed.createComponent(RootCmpWithLink); + advance(f); - router.initialNavigation(); - advance(f); + const link = f.debugElement.nativeElement.querySelector('a'); + expect(link.className).toEqual(''); - expect(link.className).toEqual('active'); - }))); + router.initialNavigation(); + advance(f); + + expect(link.className).toEqual('active'); + })); it('should set the class on a parent element when the link is active', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [{ - path: 'link', - component: DummyLinkWithParentCmp, - children: - [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] - }] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [{ + path: 'link', + component: DummyLinkWithParentCmp, + children: + [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] + }] + }]); - router.navigateByUrl('/team/22/link;exact=true'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link;exact=true'); + router.navigateByUrl('/team/22/link;exact=true'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link;exact=true'); - const native = fixture.debugElement.nativeElement.querySelector('link-parent'); - expect(native.className).toEqual('active'); + const native = fixture.debugElement.nativeElement.querySelector('link-parent'); + expect(native.className).toEqual('active'); - router.navigateByUrl('/team/22/link/simple'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link/simple'); - expect(native.className).toEqual(''); - }))); + router.navigateByUrl('/team/22/link/simple'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link/simple'); + expect(native.className).toEqual(''); + }))); it('should set the class when the link is active', - fakeAsync(inject( - [Router, TestComponentBuilder, Location], - (router: Router, tcb: TestComponentBuilder, location: Location) => { - const fixture = createRoot(tcb, router, RootCmp); + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); - router.resetConfig([{ - path: 'team/:id', - component: TeamCmp, - children: [{ - path: 'link', - component: DummyLinkCmp, - children: - [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] - }] - }]); + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [{ + path: 'link', + component: DummyLinkCmp, + children: + [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}] + }] + }]); - router.navigateByUrl('/team/22/link'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link'); + router.navigateByUrl('/team/22/link'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link'); - const native = fixture.debugElement.nativeElement.querySelector('a'); - expect(native.className).toEqual('active'); + const native = fixture.debugElement.nativeElement.querySelector('a'); + expect(native.className).toEqual('active'); - router.navigateByUrl('/team/22/link/simple'); - advance(fixture); - expect(location.path()).toEqual('/team/22/link/simple'); - expect(native.className).toEqual('active'); - }))); + router.navigateByUrl('/team/22/link/simple'); + advance(fixture); + expect(location.path()).toEqual('/team/22/link/simple'); + expect(native.className).toEqual('active'); + }))); }); describe('lazy loading', () => { it('works', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location, - loader: SpyNgModuleFactoryLoader) => { + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { @Component({ selector: 'lazy', template: 'lazy-loaded-parent []', @@ -1495,7 +1407,7 @@ describe('Integration', () => { loader.stubbedModules = {expected: LoadedModule}; - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); @@ -1509,9 +1421,8 @@ describe('Integration', () => { it('should combine routes from multiple modules into a single configuration', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location, - loader: SpyNgModuleFactoryLoader) => { + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { @Component({selector: 'lazy', template: 'lazy-loaded-2'}) class LazyComponent2 { } @@ -1542,7 +1453,7 @@ describe('Integration', () => { loader.stubbedModules = {expected1: LoadedModule, expected2: SiblingOfLoadedModule}; - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([ {path: 'lazy1', loadChildren: 'expected1'}, @@ -1560,9 +1471,8 @@ describe('Integration', () => { it('should use the injector of the lazily-loaded configuration', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location, - loader: SpyNgModuleFactoryLoader) => { + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { class LazyLoadedServiceDefinedInModule {} class LazyLoadedServiceDefinedInCmp {} @@ -1598,7 +1508,7 @@ describe('Integration', () => { loader.stubbedModules = {expected: LoadedModule}; - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); @@ -1611,8 +1521,7 @@ describe('Integration', () => { it('works when given a callback', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location) => { + [Router, Location, NgModuleFactoryLoader], (router: Router, location: Location) => { @Component({selector: 'lazy', template: 'lazy-loaded'}) class LazyLoadedComponent { } @@ -1625,7 +1534,7 @@ describe('Integration', () => { class LoadedModule { } - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]); @@ -1638,11 +1547,10 @@ describe('Integration', () => { it('error emit an error when cannot load a config', fakeAsync(inject( - [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], - (router: Router, tcb: TestComponentBuilder, location: Location, - loader: SpyNgModuleFactoryLoader) => { + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { loader.stubbedModules = {}; - const fixture = createRoot(tcb, router, RootCmp); + const fixture = createRoot(router, RootCmp); router.resetConfig([{path: 'lazy', loadChildren: 'invalid'}]); @@ -1791,9 +1699,9 @@ class QueryParamsAndFragmentCmp { name: Observable; fragment: Observable; - constructor(router: Router) { - this.name = router.routerState.queryParams.map(p => p['name']); - this.fragment = router.routerState.fragment; + constructor(route: ActivatedRoute) { + this.name = route.queryParams.map(p => p['name']); + this.fragment = route.fragment; } } @@ -1876,8 +1784,8 @@ function advance(fixture: ComponentFixture): void { fixture.detectChanges(); } -function createRoot(tcb: TestComponentBuilder, router: Router, type: any): ComponentFixture { - const f = tcb.createFakeAsync(type); +function createRoot(router: Router, type: any): ComponentFixture { + const f = TestBed.createComponent(type); advance(f); router.initialNavigation(); advance(f); diff --git a/modules/@angular/router/test/recognize.spec.ts b/modules/@angular/router/test/recognize.spec.ts index fd84b55a29..1dac06b896 100644 --- a/modules/@angular/router/test/recognize.spec.ts +++ b/modules/@angular/router/test/recognize.spec.ts @@ -631,13 +631,13 @@ describe('recognize', () => { it('should support query params', () => { const config = [{path: 'a', component: ComponentA}]; 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', () => { 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', () => { const config = [{path: 'a', component: ComponentA}]; checkRecognize( - config, 'a#f1', (s: RouterStateSnapshot) => { expect(s.fragment).toEqual('f1'); }); + config, 'a#f1', (s: RouterStateSnapshot) => { expect(s.root.fragment).toEqual('f1'); }); }); }); diff --git a/modules/@angular/router/test/resolve.spec.ts b/modules/@angular/router/test/resolve.spec.ts deleted file mode 100644 index 1f3ac16855..0000000000 --- a/modules/@angular/router/test/resolve.spec.ts +++ /dev/null @@ -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 => { - if (resolved[component]) { - return Promise.resolve(resolved[component]); - } else { - return Promise.reject('unknown component'); - } - } - }; - - recognize(RootComponent, config, tree(url), url) - .mergeMap(s => resolve(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 {} diff --git a/modules/@angular/router/testing/router_testing_module.ts b/modules/@angular/router/testing/router_testing_module.ts index a24df2491d..99ad29817b 100644 --- a/modules/@angular/router/testing/router_testing_module.ts +++ b/modules/@angular/router/testing/router_testing_module.ts @@ -8,7 +8,7 @@ import {Location, LocationStrategy} from '@angular/common'; 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 {ROUTES} from '../src/router_config_loader'; @@ -39,12 +39,10 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { } function setupTestingRouter( - resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, - location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, - routes: Route[][]) { + urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, + loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) { return new Router( - null, resolver, urlSerializer, outletMap, location, injector, loader, compiler, - flatten(routes)); + null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(routes)); } /** @@ -74,8 +72,7 @@ function setupTestingRouter( provide: Router, useFactory: setupTestingRouter, deps: [ - ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, - Compiler, Injector, ROUTES + UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, Compiler, Injector, ROUTES ] } ] diff --git a/modules/@angular/router/tsconfig.json b/modules/@angular/router/tsconfig.json index 338ceffc14..1f3ee0da11 100644 --- a/modules/@angular/router/tsconfig.json +++ b/modules/@angular/router/tsconfig.json @@ -26,7 +26,6 @@ "test/url_tree.spec.ts", "test/utils/tree.spec.ts", "test/url_serializer.spec.ts", - "test/resolve.spec.ts", "test/apply_redirects.spec.ts", "test/recognize.spec.ts", "test/create_router_state.spec.ts", diff --git a/modules/playground/src/routing/app/inbox-app.ts b/modules/playground/src/routing/app/inbox-app.ts index 9bf9c37ca6..18e7806e26 100644 --- a/modules/playground/src/routing/app/inbox-app.ts +++ b/modules/playground/src/routing/app/inbox-app.ts @@ -127,7 +127,7 @@ export class DraftsCmp { } 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'} ]; diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 4b28c76abf..64b6a7894a 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -69,7 +69,7 @@ export declare class DefaultUrlSerializer implements UrlSerializer { /** @stable */ export declare type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized; -/** @experimental */ +/** @stable */ export interface ExtraOptions { enableTracing?: boolean; useHash?: boolean; @@ -134,13 +134,7 @@ export declare type Params = { /** @experimental */ export declare const PRIMARY_OUTLET: string; -/** @experimental */ -export declare function provideRouter(config: Routes, opts?: ExtraOptions): any[]; - -/** @deprecated */ -export declare function provideRouterConfig(config: ExtraOptions): any; - -/** @deprecated */ +/** @stable */ export declare function provideRoutes(routes: Routes): any; /** @experimental */ @@ -160,7 +154,7 @@ export interface Route { canDeactivate?: any[]; canLoad?: any[]; children?: Route[]; - component?: Type | string; + component?: Type; data?: Data; loadChildren?: LoadChildren; outlet?: string; @@ -168,7 +162,6 @@ export interface Route { pathMatch?: string; redirectTo?: string; resolve?: ResolveData; - /** @deprecated */ terminal?: boolean; } /** @stable */ @@ -178,7 +171,7 @@ export declare class Router { /** @experimental */ navigated: boolean; routerState: RouterState; url: string; - constructor(rootComponentType: Type, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); + constructor(rootComponentType: Type, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree; dispose(): void; initialNavigation(): void; @@ -194,9 +187,6 @@ export declare class Router { /** @stable */ export declare const ROUTER_DIRECTIVES: (typeof RouterOutlet | typeof RouterLink | typeof RouterLinkWithHref | typeof RouterLinkActive)[]; -/** @deprecated */ -export declare type RouterConfig = Route[]; - /** @stable */ export declare class RouterLink { fragment: string; @@ -275,16 +265,12 @@ export declare class RouterOutletMap { /** @stable */ export declare class RouterState extends Tree { - /** @deprecated */ fragment: Observable; - /** @deprecated */ queryParams: Observable; snapshot: RouterStateSnapshot; toString(): string; } /** @stable */ export declare class RouterStateSnapshot extends Tree { - /** @deprecated */ fragment: string; - /** @deprecated */ queryParams: Params; url: string; toString(): string; }