Revert "fix(router): support lazy loading for empty path named outlets (#38379)"
This reverts commit 7ad32649c0d0004fcc3604c62cf0c1ae159a825b.
This commit is contained in:
parent
ac461e1efd
commit
8f24bc9443
@ -39,7 +39,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 2289,
|
"runtime-es2015": 2289,
|
||||||
"main-es2015": 245885,
|
"main-es2015": 245351,
|
||||||
"polyfills-es2015": 36938,
|
"polyfills-es2015": 36938,
|
||||||
"5-es2015": 751
|
"5-es2015": 751
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {Injector, NgModuleRef} from '@angular/core';
|
import {Injector, NgModuleRef} from '@angular/core';
|
||||||
import {defer, EmptyError, Observable, Observer, of} from 'rxjs';
|
import {defer, EmptyError, Observable, Observer, of} from 'rxjs';
|
||||||
import {catchError, first, map, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {catchError, concatAll, first, map, mergeMap, tap} from 'rxjs/operators';
|
||||||
|
|
||||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||||
import {CanLoadFn} from './interfaces';
|
import {CanLoadFn} from './interfaces';
|
||||||
@ -148,47 +148,28 @@ class ApplyRedirects {
|
|||||||
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[],
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[],
|
||||||
segments: UrlSegment[], outlet: string,
|
segments: UrlSegment[], outlet: string,
|
||||||
allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
||||||
type MatchedSegment = {segment: UrlSegmentGroup, outlet: string};
|
return of(...routes).pipe(
|
||||||
// This logic takes each route and switches to a new observable that depends on the result of
|
map((r: any) => {
|
||||||
// the previous route expansion. In this way, we compose a list of results where each one can
|
const expanded$ = this.expandSegmentAgainstRoute(
|
||||||
// depend on and look at the previous to determine how to proceed with expansion of the
|
ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
||||||
// current route.
|
return expanded$.pipe(catchError((e: any) => {
|
||||||
return routes
|
if (e instanceof NoMatch) {
|
||||||
.reduce(
|
// TODO(i): this return type doesn't match the declared Observable<UrlSegmentGroup> -
|
||||||
(accumulatedResults: Observable<Array<MatchedSegment>>, r: Route) => {
|
// talk to Jason
|
||||||
return accumulatedResults.pipe(switchMap(resultsThusFar => {
|
return of(null) as any;
|
||||||
// If we already matched a previous `Route` with the same outlet as the current,
|
}
|
||||||
// we should not process the current one.
|
throw e;
|
||||||
if (resultsThusFar.some(result => result && result.outlet === getOutlet(r))) {
|
}));
|
||||||
return of(resultsThusFar);
|
}),
|
||||||
}
|
concatAll(), first((s: any) => !!s), catchError((e: any, _: any) => {
|
||||||
const expanded$ = this.expandSegmentAgainstRoute(
|
if (e instanceof EmptyError || e.name === 'EmptyError') {
|
||||||
ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
||||||
return expanded$.pipe(
|
return of(new UrlSegmentGroup([], {}));
|
||||||
map((segment) => resultsThusFar.concat({segment, outlet: getOutlet(r)})),
|
}
|
||||||
catchError((e: any) => {
|
throw new NoMatch(segmentGroup);
|
||||||
if (e instanceof NoMatch) {
|
}
|
||||||
return of(resultsThusFar);
|
throw e;
|
||||||
}
|
}));
|
||||||
throw e;
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
of([] as MatchedSegment[]))
|
|
||||||
.pipe(
|
|
||||||
// Find the matched segment whose outlet matches the one we're looking for.
|
|
||||||
map(results => results.find(s => s.outlet === outlet)?.segment),
|
|
||||||
first((s): s is UrlSegmentGroup => s !== undefined),
|
|
||||||
catchError((e: any, _: any) => {
|
|
||||||
if (e instanceof EmptyError || e.name === 'EmptyError') {
|
|
||||||
if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
|
||||||
return of(new UrlSegmentGroup([], {}));
|
|
||||||
}
|
|
||||||
throw new NoMatch(segmentGroup);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private noLeftoversInUrl(segmentGroup: UrlSegmentGroup, segments: UrlSegment[], outlet: string):
|
private noLeftoversInUrl(segmentGroup: UrlSegmentGroup, segments: UrlSegment[], outlet: string):
|
||||||
@ -199,9 +180,7 @@ class ApplyRedirects {
|
|||||||
private expandSegmentAgainstRoute(
|
private expandSegmentAgainstRoute(
|
||||||
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[], route: Route,
|
||||||
paths: UrlSegment[], outlet: string, allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
paths: UrlSegment[], outlet: string, allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
||||||
// Empty string segments are special because multiple outlets can match a single path, i.e.
|
if (getOutlet(route) !== outlet) {
|
||||||
// `[{path: '', component: B}, {path: '', loadChildren: () => {}, outlet: "about"}]`
|
|
||||||
if (getOutlet(route) !== outlet && route.path !== '') {
|
|
||||||
return noMatch(segmentGroup);
|
return noMatch(segmentGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {NgModuleRef} from '@angular/core';
|
import {NgModuleRef} from '@angular/core';
|
||||||
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {delay, tap} from 'rxjs/operators';
|
import {delay} from 'rxjs/operators';
|
||||||
|
|
||||||
import {applyRedirects} from '../src/apply_redirects';
|
import {applyRedirects} from '../src/apply_redirects';
|
||||||
import {LoadedRouterConfig, Route, Routes} from '../src/config';
|
import {LoadedRouterConfig, Route, Routes} from '../src/config';
|
||||||
@ -482,89 +482,6 @@ describe('applyRedirects', () => {
|
|||||||
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load all matching configurations of empty path, including an auxiliary outlets',
|
|
||||||
fakeAsync(() => {
|
|
||||||
const loadedConfig =
|
|
||||||
new LoadedRouterConfig([{path: '', component: ComponentA}], testModule);
|
|
||||||
let loadCalls = 0;
|
|
||||||
let loaded: string[] = [];
|
|
||||||
const loader = {
|
|
||||||
load: (injector: any, p: Route) => {
|
|
||||||
loadCalls++;
|
|
||||||
return of(loadedConfig)
|
|
||||||
.pipe(
|
|
||||||
delay(100 * loadCalls),
|
|
||||||
tap(() => loaded.push(p.loadChildren! as string)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const config: Routes =
|
|
||||||
[{path: '', loadChildren: 'root'}, {path: '', loadChildren: 'aux', outlet: 'popup'}];
|
|
||||||
|
|
||||||
applyRedirects(testModule.injector, <any>loader, serializer, tree(''), config).subscribe();
|
|
||||||
expect(loadCalls).toBe(1);
|
|
||||||
tick(100);
|
|
||||||
expect(loaded).toEqual(['root']);
|
|
||||||
tick(200);
|
|
||||||
expect(loadCalls).toBe(2);
|
|
||||||
expect(loaded).toEqual(['root', 'aux']);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('loads only the first match when two Routes with the same outlet have the same path', () => {
|
|
||||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentA}], testModule);
|
|
||||||
let loadCalls = 0;
|
|
||||||
let loaded: string[] = [];
|
|
||||||
const loader = {
|
|
||||||
load: (injector: any, p: Route) => {
|
|
||||||
loadCalls++;
|
|
||||||
return of(loadedConfig)
|
|
||||||
.pipe(
|
|
||||||
tap(() => loaded.push(p.loadChildren! as string)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const config: Routes =
|
|
||||||
[{path: 'a', loadChildren: 'first'}, {path: 'a', loadChildren: 'second'}];
|
|
||||||
|
|
||||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('a'), config).subscribe();
|
|
||||||
expect(loadCalls).toBe(1);
|
|
||||||
expect(loaded).toEqual(['first']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load the configuration of empty root path if the entry is an aux outlet',
|
|
||||||
fakeAsync(() => {
|
|
||||||
const loadedConfig =
|
|
||||||
new LoadedRouterConfig([{path: '', component: ComponentA}], testModule);
|
|
||||||
let loaded: string[] = [];
|
|
||||||
const rootDelay = 100;
|
|
||||||
const auxDelay = 1;
|
|
||||||
const loader = {
|
|
||||||
load: (injector: any, p: Route) => {
|
|
||||||
const delayMs = p.loadChildren! as string === 'aux' ? auxDelay : rootDelay;
|
|
||||||
return of(loadedConfig)
|
|
||||||
.pipe(
|
|
||||||
delay(delayMs),
|
|
||||||
tap(() => loaded.push(p.loadChildren! as string)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const config: Routes = [
|
|
||||||
// Define aux route first so it matches before the primary outlet
|
|
||||||
{path: 'modal', loadChildren: 'aux', outlet: 'popup'},
|
|
||||||
{path: '', loadChildren: 'root'},
|
|
||||||
];
|
|
||||||
|
|
||||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('(popup:modal)'), config)
|
|
||||||
.subscribe();
|
|
||||||
tick(auxDelay);
|
|
||||||
expect(loaded).toEqual(['aux']);
|
|
||||||
tick(rootDelay);
|
|
||||||
expect(loaded).toEqual(['aux', 'root']);
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('empty paths', () => {
|
describe('empty paths', () => {
|
||||||
@ -837,46 +754,6 @@ describe('applyRedirects', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('multiple matches with empty path named outlets', () => {
|
|
||||||
it('should work with redirects when other outlet comes before the one being activated', () => {
|
|
||||||
applyRedirects(
|
|
||||||
testModule.injector, null!, serializer, tree(''),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
children: [
|
|
||||||
{path: '', component: ComponentA, outlet: 'aux'},
|
|
||||||
{path: '', redirectTo: 'b', pathMatch: 'full'},
|
|
||||||
{path: 'b', component: ComponentB},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.subscribe(
|
|
||||||
(tree: UrlTree) => {
|
|
||||||
expect(tree.toString()).toEqual('/b');
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
fail('should not be reached');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when entry point is named outlet', () => {
|
|
||||||
applyRedirects(
|
|
||||||
testModule.injector, null!, serializer, tree('(popup:modal)'),
|
|
||||||
[
|
|
||||||
{path: '', component: ComponentA},
|
|
||||||
{path: 'modal', component: ComponentB, outlet: 'popup'},
|
|
||||||
])
|
|
||||||
.subscribe(
|
|
||||||
(tree: UrlTree) => {
|
|
||||||
expect(tree.toString()).toEqual('/(popup:modal)');
|
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
fail('should not be reached' + e.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('redirecting to named outlets', () => {
|
describe('redirecting to named outlets', () => {
|
||||||
it('should work when using absolute redirects', () => {
|
it('should work when using absolute redirects', () => {
|
||||||
checkRedirect(
|
checkRedirect(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user