fix(router): should run resolvers for the same route concurrently
Fixes #14279
This commit is contained in:
parent
2a2fe11e8d
commit
ad3029e786
@ -17,6 +17,7 @@ import {of } from 'rxjs/observable/of';
|
|||||||
import {concatMap} from 'rxjs/operator/concatMap';
|
import {concatMap} from 'rxjs/operator/concatMap';
|
||||||
import {every} from 'rxjs/operator/every';
|
import {every} from 'rxjs/operator/every';
|
||||||
import {first} from 'rxjs/operator/first';
|
import {first} from 'rxjs/operator/first';
|
||||||
|
import {last} from 'rxjs/operator/last';
|
||||||
import {map} from 'rxjs/operator/map';
|
import {map} from 'rxjs/operator/map';
|
||||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||||
import {reduce} from 'rxjs/operator/reduce';
|
import {reduce} from 'rxjs/operator/reduce';
|
||||||
@ -1004,11 +1005,29 @@ export class PreActivation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
return waitForMap(resolve, (k, v) => {
|
const keys = Object.keys(resolve);
|
||||||
const resolver = this.getToken(v, future);
|
if (keys.length === 0) {
|
||||||
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
return of ({});
|
||||||
wrapIntoObservable(resolver(future, this.future));
|
}
|
||||||
|
if (keys.length === 1) {
|
||||||
|
const key = keys[0];
|
||||||
|
return map.call(
|
||||||
|
this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
|
||||||
|
}
|
||||||
|
const data: {[k: string]: any} = {};
|
||||||
|
const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
|
||||||
|
return map.call(this.getResolver(resolve[key], future), (value: any) => {
|
||||||
|
data[key] = value;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return map.call(last.call(runningResolvers$), () => data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const resolver = this.getToken(injectionToken, future);
|
||||||
|
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
||||||
|
wrapIntoObservable(resolver(future, this.future));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
||||||
|
@ -13,6 +13,8 @@ import {By} from '@angular/platform-browser/src/dom/debug/by';
|
|||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
import {Observer} from 'rxjs/Observer';
|
||||||
|
import {of } from 'rxjs/observable/of';
|
||||||
import {map} from 'rxjs/operator/map';
|
import {map} from 'rxjs/operator/map';
|
||||||
|
|
||||||
import {forEach} from '../src/utils/collection';
|
import {forEach} from '../src/utils/collection';
|
||||||
@ -913,13 +915,12 @@ describe('Integration', () => {
|
|||||||
{provide: 'resolveFour', useValue: (a: any, b: any) => 4},
|
{provide: 'resolveFour', useValue: (a: any, b: any) => 4},
|
||||||
{provide: 'resolveSix', useClass: ResolveSix},
|
{provide: 'resolveSix', useClass: ResolveSix},
|
||||||
{provide: 'resolveError', useValue: (a: any, b: any) => Promise.reject('error')},
|
{provide: 'resolveError', useValue: (a: any, b: any) => Promise.reject('error')},
|
||||||
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length}
|
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should provide resolved data',
|
it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => {
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
|
||||||
const fixture = createRoot(router, RootCmpWithTwoOutlets);
|
const fixture = createRoot(router, RootCmpWithTwoOutlets);
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
@ -1025,6 +1026,57 @@ describe('Integration', () => {
|
|||||||
|
|
||||||
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
|
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
describe('should run resolvers for the same route concurrently', () => {
|
||||||
|
let log: string[];
|
||||||
|
let observer: Observer<any>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
log = [];
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: 'resolver1',
|
||||||
|
useValue: () => {
|
||||||
|
const obs$ = new Observable((obs: Observer<any>) => {
|
||||||
|
observer = obs;
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
|
return map.call(obs$, () => log.push('resolver1'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: 'resolver2',
|
||||||
|
useValue: () => {
|
||||||
|
return map.call(of (null), () => {
|
||||||
|
log.push('resolver2');
|
||||||
|
observer.next(null);
|
||||||
|
observer.complete()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works', fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([{
|
||||||
|
path: 'a',
|
||||||
|
resolve: {
|
||||||
|
one: 'resolver1',
|
||||||
|
two: 'resolver2',
|
||||||
|
},
|
||||||
|
component: SimpleCmp
|
||||||
|
}]);
|
||||||
|
|
||||||
|
router.navigateByUrl('/a');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(log).toEqual(['resolver2', 'resolver1']);
|
||||||
|
})));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('router links', () => {
|
describe('router links', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user