fix(router): lazily-loaded modules should use loaded injectors instead of the root one
This commit is contained in:
parent
a5dc5705a3
commit
85be729c70
|
@ -10,13 +10,14 @@ import 'rxjs/add/operator/first';
|
||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
import 'rxjs/add/operator/concatAll';
|
import 'rxjs/add/operator/concatAll';
|
||||||
|
|
||||||
|
import {Injector} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
import {EmptyError} from 'rxjs/util/EmptyError';
|
import {EmptyError} from 'rxjs/util/EmptyError';
|
||||||
|
|
||||||
import {Route, Routes} from './config';
|
import {Route, Routes} from './config';
|
||||||
import {RouterConfigLoader} from './router_config_loader';
|
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||||
import {PRIMARY_OUTLET} from './shared';
|
import {PRIMARY_OUTLET} from './shared';
|
||||||
import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree';
|
import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree';
|
||||||
import {merge, waitForMap} from './utils/collection';
|
import {merge, waitForMap} from './utils/collection';
|
||||||
|
@ -38,8 +39,9 @@ function absoluteRedirect(newPaths: UrlPathWithParams[]): Observable<UrlSegment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyRedirects(
|
export function applyRedirects(
|
||||||
configLoader: RouterConfigLoader, urlTree: UrlTree, config: Routes): Observable<UrlTree> {
|
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
|
||||||
return expandSegment(configLoader, config, urlTree.root, PRIMARY_OUTLET)
|
config: Routes): Observable<UrlTree> {
|
||||||
|
return expandSegment(injector, configLoader, config, urlTree.root, PRIMARY_OUTLET)
|
||||||
.map(rootSegment => createUrlTree(urlTree, rootSegment))
|
.map(rootSegment => createUrlTree(urlTree, rootSegment))
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
if (e instanceof AbsoluteRedirect) {
|
if (e instanceof AbsoluteRedirect) {
|
||||||
|
@ -61,33 +63,33 @@ function createUrlTree(urlTree: UrlTree, rootCandidate: UrlSegment): UrlTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandSegment(
|
function expandSegment(
|
||||||
configLoader: RouterConfigLoader, routes: Route[], segment: UrlSegment,
|
injector: Injector, configLoader: RouterConfigLoader, routes: Route[], segment: UrlSegment,
|
||||||
outlet: string): Observable<UrlSegment> {
|
outlet: string): Observable<UrlSegment> {
|
||||||
if (segment.pathsWithParams.length === 0 && segment.hasChildren()) {
|
if (segment.pathsWithParams.length === 0 && segment.hasChildren()) {
|
||||||
return expandSegmentChildren(configLoader, routes, segment)
|
return expandSegmentChildren(injector, configLoader, routes, segment)
|
||||||
.map(children => new UrlSegment([], children));
|
.map(children => new UrlSegment([], children));
|
||||||
} else {
|
} else {
|
||||||
return expandPathsWithParams(
|
return expandPathsWithParams(
|
||||||
configLoader, segment, routes, segment.pathsWithParams, outlet, true);
|
injector, configLoader, segment, routes, segment.pathsWithParams, outlet, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandSegmentChildren(
|
function expandSegmentChildren(
|
||||||
configLoader: RouterConfigLoader, routes: Route[],
|
injector: Injector, configLoader: RouterConfigLoader, routes: Route[],
|
||||||
segment: UrlSegment): Observable<{[name: string]: UrlSegment}> {
|
segment: UrlSegment): Observable<{[name: string]: UrlSegment}> {
|
||||||
return waitForMap(
|
return waitForMap(
|
||||||
segment.children,
|
segment.children,
|
||||||
(childOutlet, child) => expandSegment(configLoader, routes, child, childOutlet));
|
(childOutlet, child) => expandSegment(injector, configLoader, routes, child, childOutlet));
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandPathsWithParams(
|
function expandPathsWithParams(
|
||||||
configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[],
|
injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[],
|
||||||
paths: UrlPathWithParams[], outlet: string, allowRedirects: boolean): Observable<UrlSegment> {
|
paths: UrlPathWithParams[], outlet: string, allowRedirects: boolean): Observable<UrlSegment> {
|
||||||
const processRoutes =
|
const processRoutes =
|
||||||
of (...routes)
|
of (...routes)
|
||||||
.map(r => {
|
.map(r => {
|
||||||
return expandPathsWithParamsAgainstRoute(
|
return expandPathsWithParamsAgainstRoute(
|
||||||
configLoader, segment, routes, r, paths, outlet, allowRedirects)
|
injector, configLoader, segment, routes, r, paths, outlet, allowRedirects)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (e instanceof NoMatch)
|
if (e instanceof NoMatch)
|
||||||
return of (null);
|
return of (null);
|
||||||
|
@ -107,27 +109,28 @@ function expandPathsWithParams(
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandPathsWithParamsAgainstRoute(
|
function expandPathsWithParamsAgainstRoute(
|
||||||
configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], route: Route,
|
injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[],
|
||||||
paths: UrlPathWithParams[], outlet: string, allowRedirects: boolean): Observable<UrlSegment> {
|
route: Route, paths: UrlPathWithParams[], outlet: string,
|
||||||
|
allowRedirects: boolean): Observable<UrlSegment> {
|
||||||
if (getOutlet(route) !== outlet) return noMatch(segment);
|
if (getOutlet(route) !== outlet) return noMatch(segment);
|
||||||
if (route.redirectTo !== undefined && !allowRedirects) return noMatch(segment);
|
if (route.redirectTo !== undefined && !allowRedirects) return noMatch(segment);
|
||||||
|
|
||||||
if (route.redirectTo !== undefined) {
|
if (route.redirectTo !== undefined) {
|
||||||
return expandPathsWithParamsAgainstRouteUsingRedirect(
|
return expandPathsWithParamsAgainstRouteUsingRedirect(
|
||||||
configLoader, segment, routes, route, paths, outlet);
|
injector, configLoader, segment, routes, route, paths, outlet);
|
||||||
} else {
|
} else {
|
||||||
return matchPathsWithParamsAgainstRoute(configLoader, segment, route, paths);
|
return matchPathsWithParamsAgainstRoute(injector, configLoader, segment, route, paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandPathsWithParamsAgainstRouteUsingRedirect(
|
function expandPathsWithParamsAgainstRouteUsingRedirect(
|
||||||
configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], route: Route,
|
injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[],
|
||||||
paths: UrlPathWithParams[], outlet: string): Observable<UrlSegment> {
|
route: Route, paths: UrlPathWithParams[], outlet: string): Observable<UrlSegment> {
|
||||||
if (route.path === '**') {
|
if (route.path === '**') {
|
||||||
return expandWildCardWithParamsAgainstRouteUsingRedirect(route);
|
return expandWildCardWithParamsAgainstRouteUsingRedirect(route);
|
||||||
} else {
|
} else {
|
||||||
return expandRegularPathWithParamsAgainstRouteUsingRedirect(
|
return expandRegularPathWithParamsAgainstRouteUsingRedirect(
|
||||||
configLoader, segment, routes, route, paths, outlet);
|
injector, configLoader, segment, routes, route, paths, outlet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +144,8 @@ function expandWildCardWithParamsAgainstRouteUsingRedirect(route: Route): Observ
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandRegularPathWithParamsAgainstRouteUsingRedirect(
|
function expandRegularPathWithParamsAgainstRouteUsingRedirect(
|
||||||
configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], route: Route,
|
injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[],
|
||||||
paths: UrlPathWithParams[], outlet: string): Observable<UrlSegment> {
|
route: Route, paths: UrlPathWithParams[], outlet: string): Observable<UrlSegment> {
|
||||||
const {matched, consumedPaths, lastChild, positionalParamSegments} = match(segment, route, paths);
|
const {matched, consumedPaths, lastChild, positionalParamSegments} = match(segment, route, paths);
|
||||||
if (!matched) return noMatch(segment);
|
if (!matched) return noMatch(segment);
|
||||||
|
|
||||||
|
@ -152,12 +155,13 @@ function expandRegularPathWithParamsAgainstRouteUsingRedirect(
|
||||||
return absoluteRedirect(newPaths);
|
return absoluteRedirect(newPaths);
|
||||||
} else {
|
} else {
|
||||||
return expandPathsWithParams(
|
return expandPathsWithParams(
|
||||||
configLoader, segment, routes, newPaths.concat(paths.slice(lastChild)), outlet, false);
|
injector, configLoader, segment, routes, newPaths.concat(paths.slice(lastChild)), outlet,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchPathsWithParamsAgainstRoute(
|
function matchPathsWithParamsAgainstRoute(
|
||||||
configLoader: RouterConfigLoader, rawSegment: UrlSegment, route: Route,
|
injector: Injector, configLoader: RouterConfigLoader, rawSegment: UrlSegment, route: Route,
|
||||||
paths: UrlPathWithParams[]): Observable<UrlSegment> {
|
paths: UrlPathWithParams[]): Observable<UrlSegment> {
|
||||||
if (route.path === '**') {
|
if (route.path === '**') {
|
||||||
return of (new UrlSegment(paths, {}));
|
return of (new UrlSegment(paths, {}));
|
||||||
|
@ -168,11 +172,13 @@ function matchPathsWithParamsAgainstRoute(
|
||||||
|
|
||||||
const rawSlicedPath = paths.slice(lastChild);
|
const rawSlicedPath = paths.slice(lastChild);
|
||||||
|
|
||||||
return getChildConfig(configLoader, route).mergeMap(childConfig => {
|
return getChildConfig(injector, configLoader, route).mergeMap(routerConfig => {
|
||||||
|
const childInjector = routerConfig.injector;
|
||||||
|
const childConfig = routerConfig.routes;
|
||||||
const {segment, slicedPath} = split(rawSegment, consumedPaths, rawSlicedPath, childConfig);
|
const {segment, slicedPath} = split(rawSegment, consumedPaths, rawSlicedPath, childConfig);
|
||||||
|
|
||||||
if (slicedPath.length === 0 && segment.hasChildren()) {
|
if (slicedPath.length === 0 && segment.hasChildren()) {
|
||||||
return expandSegmentChildren(configLoader, childConfig, segment)
|
return expandSegmentChildren(childInjector, configLoader, childConfig, segment)
|
||||||
.map(children => new UrlSegment(consumedPaths, children));
|
.map(children => new UrlSegment(consumedPaths, children));
|
||||||
|
|
||||||
} else if (childConfig.length === 0 && slicedPath.length === 0) {
|
} else if (childConfig.length === 0 && slicedPath.length === 0) {
|
||||||
|
@ -180,23 +186,25 @@ function matchPathsWithParamsAgainstRoute(
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return expandPathsWithParams(
|
return expandPathsWithParams(
|
||||||
configLoader, segment, childConfig, slicedPath, PRIMARY_OUTLET, true)
|
childInjector, configLoader, segment, childConfig, slicedPath, PRIMARY_OUTLET,
|
||||||
|
true)
|
||||||
.map(cs => new UrlSegment(consumedPaths.concat(cs.pathsWithParams), cs.children));
|
.map(cs => new UrlSegment(consumedPaths.concat(cs.pathsWithParams), cs.children));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChildConfig(configLoader: RouterConfigLoader, route: Route): Observable<Route[]> {
|
function getChildConfig(injector: Injector, configLoader: RouterConfigLoader, route: Route):
|
||||||
|
Observable<LoadedRouterConfig> {
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
return of (route.children);
|
return of (new LoadedRouterConfig(route.children, injector, null));
|
||||||
} else if (route.loadChildren) {
|
} else if (route.loadChildren) {
|
||||||
return configLoader.load(route.loadChildren).map(r => {
|
return configLoader.load(injector, route.loadChildren).map(r => {
|
||||||
(<any>route)._loadedConfig = r;
|
(<any>route)._loadedConfig = r;
|
||||||
return r.routes;
|
return r;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return of ([]);
|
return of (new LoadedRouterConfig([], injector, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||||
*/
|
*/
|
||||||
export interface CanActivate {
|
export interface CanActivate {
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
Observable<boolean>|boolean;
|
Observable<boolean>|Promise<boolean>|boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,7 +114,7 @@ export interface CanActivate {
|
||||||
|
|
||||||
export interface CanActivateChild {
|
export interface CanActivateChild {
|
||||||
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
Observable<boolean>|boolean;
|
Observable<boolean>|Promise<boolean>|boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,7 +161,7 @@ export interface CanActivateChild {
|
||||||
*/
|
*/
|
||||||
export interface CanDeactivate<T> {
|
export interface CanDeactivate<T> {
|
||||||
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
Observable<boolean>|boolean;
|
Observable<boolean>|Promise<boolean>|boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,5 +197,6 @@ export interface CanDeactivate<T> {
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export interface Resolve<T> {
|
export interface Resolve<T> {
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|any;
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
|
Observable<any>|Promise<any>|any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {createUrlTree} from './create_url_tree';
|
||||||
import {RouterOutlet} from './directives/router_outlet';
|
import {RouterOutlet} from './directives/router_outlet';
|
||||||
import {recognize} from './recognize';
|
import {recognize} from './recognize';
|
||||||
import {resolve} from './resolve';
|
import {resolve} from './resolve';
|
||||||
import {RouterConfigLoader} from './router_config_loader';
|
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||||
import {RouterOutletMap} from './router_outlet_map';
|
import {RouterOutletMap} from './router_outlet_map';
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||||
import {PRIMARY_OUTLET, Params} from './shared';
|
import {PRIMARY_OUTLET, Params} from './shared';
|
||||||
|
@ -315,7 +315,7 @@ export class Router {
|
||||||
const storedState = this.currentRouterState;
|
const storedState = this.currentRouterState;
|
||||||
const storedUrl = this.currentUrlTree;
|
const storedUrl = this.currentUrlTree;
|
||||||
|
|
||||||
applyRedirects(this.configLoader, url, this.config)
|
applyRedirects(this.injector, this.configLoader, url, this.config)
|
||||||
.mergeMap(u => {
|
.mergeMap(u => {
|
||||||
appliedUrl = u;
|
appliedUrl = u;
|
||||||
return recognize(
|
return recognize(
|
||||||
|
@ -522,7 +522,7 @@ class PreActivation {
|
||||||
const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
|
const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
|
||||||
if (!canActivate || canActivate.length === 0) return Observable.of(true);
|
if (!canActivate || canActivate.length === 0) return Observable.of(true);
|
||||||
const obs = Observable.from(canActivate).map(c => {
|
const obs = Observable.from(canActivate).map(c => {
|
||||||
const guard = this.injector.get(c);
|
const guard = this.getToken(c, future, this.future);
|
||||||
if (guard.canActivate) {
|
if (guard.canActivate) {
|
||||||
return wrapIntoObservable(guard.canActivate(future, this.future));
|
return wrapIntoObservable(guard.canActivate(future, this.future));
|
||||||
} else {
|
} else {
|
||||||
|
@ -535,37 +535,37 @@ class PreActivation {
|
||||||
private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable<boolean> {
|
private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable<boolean> {
|
||||||
const future = path[path.length - 1];
|
const future = path[path.length - 1];
|
||||||
|
|
||||||
const canActivateChildGuards =
|
const canActivateChildGuards = path.slice(0, path.length - 1)
|
||||||
path.slice(0, path.length - 1)
|
|
||||||
.reverse()
|
.reverse()
|
||||||
.map(p => {
|
.map(p => this.extractCanActivateChild(p))
|
||||||
const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
|
|
||||||
if (!canActivateChild || canActivateChild.length === 0) return null;
|
|
||||||
return {snapshot: future, node: p, guards: canActivateChild};
|
|
||||||
})
|
|
||||||
.filter(_ => _ !== null);
|
.filter(_ => _ !== null);
|
||||||
|
|
||||||
return andObservables(Observable.from(canActivateChildGuards).map(d => {
|
return andObservables(Observable.from(canActivateChildGuards).map(d => {
|
||||||
const obs = Observable.from(d.guards).map(c => {
|
const obs = Observable.from(d.guards).map(c => {
|
||||||
const guard = this.injector.get(c);
|
const guard = this.getToken(c, c.node, this.future);
|
||||||
if (guard.canActivateChild) {
|
if (guard.canActivateChild) {
|
||||||
return wrapIntoObservable(guard.canActivateChild(d.snapshot, this.future));
|
return wrapIntoObservable(guard.canActivateChild(future, this.future));
|
||||||
} else {
|
} else {
|
||||||
return wrapIntoObservable(guard(d.snapshot, this.future));
|
return wrapIntoObservable(guard(future, this.future));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return andObservables(obs);
|
return andObservables(obs);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractCanActivateChild(p: ActivatedRouteSnapshot):
|
||||||
|
{node: ActivatedRouteSnapshot, guards: any[]} {
|
||||||
|
const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
|
||||||
|
if (!canActivateChild || canActivateChild.length === 0) return null;
|
||||||
|
return {node: p, guards: canActivateChild};
|
||||||
|
}
|
||||||
|
|
||||||
private runCanDeactivate(component: Object, curr: ActivatedRouteSnapshot): Observable<boolean> {
|
private runCanDeactivate(component: Object, curr: ActivatedRouteSnapshot): Observable<boolean> {
|
||||||
const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
|
const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
|
||||||
if (!canDeactivate || canDeactivate.length === 0) return Observable.of(true);
|
if (!canDeactivate || canDeactivate.length === 0) return Observable.of(true);
|
||||||
return Observable.from(canDeactivate)
|
return Observable.from(canDeactivate)
|
||||||
.map(c => {
|
.map(c => {
|
||||||
const guard = this.injector.get(c);
|
const guard = this.getToken(c, curr, this.curr);
|
||||||
|
|
||||||
if (guard.canDeactivate) {
|
if (guard.canDeactivate) {
|
||||||
return wrapIntoObservable(guard.canDeactivate(component, curr, this.curr));
|
return wrapIntoObservable(guard.canDeactivate(component, curr, this.curr));
|
||||||
} else {
|
} else {
|
||||||
|
@ -587,11 +587,17 @@ class PreActivation {
|
||||||
|
|
||||||
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
return waitForMap(resolve, (k, v) => {
|
return waitForMap(resolve, (k, v) => {
|
||||||
const resolver = this.injector.get(v);
|
const resolver = this.getToken(v, future, this.future);
|
||||||
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
||||||
wrapIntoObservable(resolver(future, this.future));
|
wrapIntoObservable(resolver(future, this.future));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getToken(token: any, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
|
||||||
|
const config = closestLoadedConfig(state, snapshot);
|
||||||
|
const injector = config ? config.injector : this.injector;
|
||||||
|
return injector.get(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
|
function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
|
||||||
|
@ -685,12 +691,11 @@ class ActivateRoutes {
|
||||||
useValue: outletMap
|
useValue: outletMap
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const parentFuture = this.futureState.parent(future); // find the closest parent?
|
const config = closestLoadedConfig(this.futureState.snapshot, future.snapshot);
|
||||||
const config = parentFuture ? parentFuture.snapshot._routeConfig : null;
|
|
||||||
let loadedFactoryResolver: ComponentFactoryResolver = null;
|
let loadedFactoryResolver: ComponentFactoryResolver = null;
|
||||||
|
|
||||||
if (config && (<any>config)._loadedConfig) {
|
if (config) {
|
||||||
const loadedResolver = (<any>config)._loadedConfig.factoryResolver;
|
const loadedResolver = config.factoryResolver;
|
||||||
loadedFactoryResolver = loadedResolver;
|
loadedFactoryResolver = loadedResolver;
|
||||||
resolved.push({provide: ComponentFactoryResolver, useValue: loadedResolver});
|
resolved.push({provide: ComponentFactoryResolver, useValue: loadedResolver});
|
||||||
};
|
};
|
||||||
|
@ -710,6 +715,15 @@ class ActivateRoutes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closestLoadedConfig(
|
||||||
|
state: RouterStateSnapshot, snapshot: ActivatedRouteSnapshot): LoadedRouterConfig {
|
||||||
|
const b = state.pathFromRoot(snapshot).filter(s => {
|
||||||
|
const config = (<any>s)._routeConfig;
|
||||||
|
return config && config._loadedConfig && s !== snapshot;
|
||||||
|
});
|
||||||
|
return b.length > 0 ? (<any>b[b.length - 1])._routeConfig._loadedConfig : null;
|
||||||
|
}
|
||||||
|
|
||||||
function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
||||||
return observables.mergeAll().every(result => result === true);
|
return observables.mergeAll().every(result => result === true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AppModuleFactoryLoader, AppModuleRef, ComponentFactoryResolver, OpaqueToken} from '@angular/core';
|
import {AppModuleFactoryLoader, ComponentFactoryResolver, Injector, OpaqueToken} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||||
|
|
||||||
import {Route} from './config';
|
import {Route} from './config';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use Routes
|
* @deprecated use Routes
|
||||||
*/
|
*/
|
||||||
|
@ -19,16 +20,19 @@ export const ROUTER_CONFIG = new OpaqueToken('ROUTER_CONFIG');
|
||||||
export const ROUTES = new OpaqueToken('ROUTES');
|
export const ROUTES = new OpaqueToken('ROUTES');
|
||||||
|
|
||||||
export class LoadedRouterConfig {
|
export class LoadedRouterConfig {
|
||||||
constructor(public routes: Route[], public factoryResolver: ComponentFactoryResolver) {}
|
constructor(
|
||||||
|
public routes: Route[], public injector: Injector,
|
||||||
|
public factoryResolver: ComponentFactoryResolver) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RouterConfigLoader {
|
export class RouterConfigLoader {
|
||||||
constructor(private loader: AppModuleFactoryLoader) {}
|
constructor(private loader: AppModuleFactoryLoader) {}
|
||||||
|
|
||||||
load(path: string): Observable<LoadedRouterConfig> {
|
load(parentInjector: Injector, path: string): Observable<LoadedRouterConfig> {
|
||||||
return fromPromise(this.loader.load(path).then(r => {
|
return fromPromise(this.loader.load(path).then(r => {
|
||||||
const ref = r.create();
|
const ref = r.create(parentInjector);
|
||||||
return new LoadedRouterConfig(ref.injector.get(ROUTES), ref.componentFactoryResolver);
|
return new LoadedRouterConfig(
|
||||||
|
ref.injector.get(ROUTES), ref.injector, ref.componentFactoryResolver);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when cannot handle a positional parameter', () => {
|
it('should throw when cannot handle a positional parameter', () => {
|
||||||
applyRedirects(null, tree('/a/1'), [
|
applyRedirects(null, null, tree('/a/1'), [
|
||||||
{path: 'a/:id', redirectTo: 'a/:other'}
|
{path: 'a/:id', redirectTo: 'a/:other'}
|
||||||
]).subscribe(() => {}, (e) => {
|
]).subscribe(() => {}, (e) => {
|
||||||
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
||||||
|
@ -133,12 +133,17 @@ describe('applyRedirects', () => {
|
||||||
|
|
||||||
describe('lazy loading', () => {
|
describe('lazy loading', () => {
|
||||||
it('should load config on demand', () => {
|
it('should load config on demand', () => {
|
||||||
const loadedConfig =
|
const loadedConfig = new LoadedRouterConfig(
|
||||||
new LoadedRouterConfig([{path: 'b', component: ComponentB}], <any>'stubFactoryResolver');
|
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver');
|
||||||
const loader = {load: (p: any) => of (loadedConfig)};
|
const loader = {
|
||||||
|
load: (injector: any, p: any) => {
|
||||||
|
if (injector !== 'providedInjector') throw 'Invalid Injector';
|
||||||
|
return of (loadedConfig);
|
||||||
|
}
|
||||||
|
};
|
||||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>loader, tree('a/b'), config).forEach(r => {
|
applyRedirects(<any>'providedInjector', <any>loader, tree('a/b'), config).forEach(r => {
|
||||||
compareTrees(r, tree('/a/b'));
|
compareTrees(r, tree('/a/b'));
|
||||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||||
});
|
});
|
||||||
|
@ -150,7 +155,7 @@ describe('applyRedirects', () => {
|
||||||
};
|
};
|
||||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||||
|
|
||||||
applyRedirects(<any>loader, tree('a/b'), config).subscribe(() => {}, (e) => {
|
applyRedirects(null, <any>loader, tree('a/b'), config).subscribe(() => {}, (e) => {
|
||||||
expect(e.message).toEqual('Loading Error');
|
expect(e.message).toEqual('Loading Error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -199,7 +204,7 @@ describe('applyRedirects', () => {
|
||||||
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
||||||
];
|
];
|
||||||
|
|
||||||
applyRedirects(null, tree('b'), config)
|
applyRedirects(null, null, tree('b'), config)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(_) => { throw 'Should not be reached'; },
|
(_) => { throw 'Should not be reached'; },
|
||||||
e => { expect(e.message).toEqual('Cannot match any routes: \'b\''); });
|
e => { expect(e.message).toEqual('Cannot match any routes: \'b\''); });
|
||||||
|
@ -329,7 +334,7 @@ describe('applyRedirects', () => {
|
||||||
]
|
]
|
||||||
}];
|
}];
|
||||||
|
|
||||||
applyRedirects(null, tree('a/(d//aux:e)'), config)
|
applyRedirects(null, null, tree('a/(d//aux:e)'), config)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(_) => { throw 'Should not be reached'; },
|
(_) => { throw 'Should not be reached'; },
|
||||||
e => { expect(e.message).toEqual('Cannot match any routes: \'a\''); });
|
e => { expect(e.message).toEqual('Cannot match any routes: \'a\''); });
|
||||||
|
@ -339,7 +344,7 @@ describe('applyRedirects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkRedirect(config: Routes, url: string, callback: any): void {
|
function checkRedirect(config: Routes, url: string, callback: any): void {
|
||||||
applyRedirects(null, tree(url), config).subscribe(callback, e => { throw e; });
|
applyRedirects(null, null, tree(url), config).subscribe(callback, e => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
||||||
function tree(url: string): UrlTree {
|
function tree(url: string): UrlTree {
|
||||||
|
|
|
@ -907,7 +907,7 @@ describe('Integration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
fit('works',
|
it('works',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, TestComponentBuilder, Location],
|
[Router, TestComponentBuilder, Location],
|
||||||
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
||||||
|
@ -1274,6 +1274,42 @@ describe('Integration', () => {
|
||||||
.toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
.toHaveText('lazy-loaded-parent [lazy-loaded-child]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should use the injector of the lazily-loaded configuration',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
|
||||||
|
(router: Router, tcb: TestComponentBuilder, location: Location,
|
||||||
|
loader: SpyAppModuleFactoryLoader) => {
|
||||||
|
@Component({selector: 'lazy', template: 'lazy-loaded', directives: ROUTER_DIRECTIVES})
|
||||||
|
class LazyLoadedComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AppModule({
|
||||||
|
precompile: [LazyLoadedComponent],
|
||||||
|
providers: [
|
||||||
|
provideRoutes([{
|
||||||
|
path: '',
|
||||||
|
canActivate: ['alwaysTrue'],
|
||||||
|
children: [{path: 'loaded', component: LazyLoadedComponent}]
|
||||||
|
}]),
|
||||||
|
{provide: 'alwaysTrue', useValue: () => true}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
class LoadedModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.stubbedModules = {expected: LoadedModule};
|
||||||
|
|
||||||
|
const fixture = createRoot(tcb, router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||||
|
|
||||||
|
router.navigateByUrl('/lazy/loaded');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(location.path()).toEqual('/lazy/loaded');
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded');
|
||||||
|
})));
|
||||||
|
|
||||||
it('error emit an error when cannot load a config',
|
it('error emit an error when cannot load a config',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
|
[Router, TestComponentBuilder, Location, AppModuleFactoryLoader],
|
||||||
|
|
|
@ -21,12 +21,17 @@ export declare class ActivatedRouteSnapshot {
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export interface CanActivate {
|
export interface CanActivate {
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean;
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @stable */
|
||||||
|
export interface CanActivateChild {
|
||||||
|
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export interface CanDeactivate<T> {
|
export interface CanDeactivate<T> {
|
||||||
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean;
|
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
@ -101,7 +106,7 @@ export declare function provideRoutes(routes: Routes): any;
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export interface Resolve<T> {
|
export interface Resolve<T> {
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | any;
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
|
Loading…
Reference in New Issue