Skip to content

Commit ad3029e

Browse files
Dzmitry Shylovichalxhub
authored andcommitted
fix(router): should run resolvers for the same route concurrently
Fixes #14279
1 parent 2a2fe11 commit ad3029e

File tree

2 files changed

+78
-7
lines changed

2 files changed

+78
-7
lines changed

packages/router/src/router.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {of } from 'rxjs/observable/of';
1717
import {concatMap} from 'rxjs/operator/concatMap';
1818
import {every} from 'rxjs/operator/every';
1919
import {first} from 'rxjs/operator/first';
20+
import {last} from 'rxjs/operator/last';
2021
import {map} from 'rxjs/operator/map';
2122
import {mergeMap} from 'rxjs/operator/mergeMap';
2223
import {reduce} from 'rxjs/operator/reduce';
@@ -1004,11 +1005,29 @@ export class PreActivation {
10041005
}
10051006

10061007
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
1007-
return waitForMap(resolve, (k, v) => {
1008-
const resolver = this.getToken(v, future);
1009-
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
1010-
wrapIntoObservable(resolver(future, this.future));
1008+
const keys = Object.keys(resolve);
1009+
if (keys.length === 0) {
1010+
return of ({});
1011+
}
1012+
if (keys.length === 1) {
1013+
const key = keys[0];
1014+
return map.call(
1015+
this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
1016+
}
1017+
const data: {[k: string]: any} = {};
1018+
const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
1019+
return map.call(this.getResolver(resolve[key], future), (value: any) => {
1020+
data[key] = value;
1021+
return value;
1022+
});
10111023
});
1024+
return map.call(last.call(runningResolvers$), () => data);
1025+
}
1026+
1027+
private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable<any> {
1028+
const resolver = this.getToken(injectionToken, future);
1029+
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
1030+
wrapIntoObservable(resolver(future, this.future));
10121031
}
10131032

10141033
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {

packages/router/test/integration.spec.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {By} from '@angular/platform-browser/src/dom/debug/by';
1313
import {expect} from '@angular/platform-browser/testing/src/matchers';
1414
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';
1515
import {Observable} from 'rxjs/Observable';
16+
import {Observer} from 'rxjs/Observer';
17+
import {of } from 'rxjs/observable/of';
1618
import {map} from 'rxjs/operator/map';
1719

1820
import {forEach} from '../src/utils/collection';
@@ -913,13 +915,12 @@ describe('Integration', () => {
913915
{provide: 'resolveFour', useValue: (a: any, b: any) => 4},
914916
{provide: 'resolveSix', useClass: ResolveSix},
915917
{provide: 'resolveError', useValue: (a: any, b: any) => Promise.reject('error')},
916-
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length}
918+
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length},
917919
]
918920
});
919921
});
920922

921-
it('should provide resolved data',
922-
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
923+
it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => {
923924
const fixture = createRoot(router, RootCmpWithTwoOutlets);
924925

925926
router.resetConfig([{
@@ -1025,6 +1026,57 @@ describe('Integration', () => {
10251026

10261027
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
10271028
})));
1029+
1030+
describe('should run resolvers for the same route concurrently', () => {
1031+
let log: string[];
1032+
let observer: Observer<any>;
1033+
1034+
beforeEach(() => {
1035+
log = [];
1036+
TestBed.configureTestingModule({
1037+
providers: [
1038+
{
1039+
provide: 'resolver1',
1040+
useValue: () => {
1041+
const obs$ = new Observable((obs: Observer<any>) => {
1042+
observer = obs;
1043+
return () => {};
1044+
});
1045+
return map.call(obs$, () => log.push('resolver1'));
1046+
}
1047+
},
1048+
{
1049+
provide: 'resolver2',
1050+
useValue: () => {
1051+
return map.call(of (null), () => {
1052+
log.push('resolver2');
1053+
observer.next(null);
1054+
observer.complete()
1055+
});
1056+
}
1057+
},
1058+
]
1059+
});
1060+
});
1061+
1062+
it('works', fakeAsync(inject([Router], (router: Router) => {
1063+
const fixture = createRoot(router, RootCmp);
1064+
1065+
router.resetConfig([{
1066+
path: 'a',
1067+
resolve: {
1068+
one: 'resolver1',
1069+
two: 'resolver2',
1070+
},
1071+
component: SimpleCmp
1072+
}]);
1073+
1074+
router.navigateByUrl('/a');
1075+
advance(fixture);
1076+
1077+
expect(log).toEqual(['resolver2', 'resolver1']);
1078+
})));
1079+
});
10281080
});
10291081

10301082
describe('router links', () => {

0 commit comments

Comments
 (0)