diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 429fc14ff6a1e..ee88cb6901aeb 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -17,6 +17,7 @@ import {of } from 'rxjs/observable/of'; import {concatMap} from 'rxjs/operator/concatMap'; import {every} from 'rxjs/operator/every'; import {first} from 'rxjs/operator/first'; +import {last} from 'rxjs/operator/last'; import {map} from 'rxjs/operator/map'; import {mergeMap} from 'rxjs/operator/mergeMap'; import {reduce} from 'rxjs/operator/reduce'; @@ -1004,11 +1005,29 @@ export class PreActivation { } private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable { - return waitForMap(resolve, (k, v) => { - const resolver = this.getToken(v, future); - return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) : - wrapIntoObservable(resolver(future, this.future)); + const keys = Object.keys(resolve); + if (keys.length === 0) { + return of ({}); + } + 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 { + 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 { diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index d89258ceaff7b..62f686fb4a016 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -13,6 +13,8 @@ import {By} from '@angular/platform-browser/src/dom/debug/by'; 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 {Observable} from 'rxjs/Observable'; +import {Observer} from 'rxjs/Observer'; +import {of } from 'rxjs/observable/of'; import {map} from 'rxjs/operator/map'; import {forEach} from '../src/utils/collection'; @@ -913,13 +915,12 @@ describe('Integration', () => { {provide: 'resolveFour', useValue: (a: any, b: any) => 4}, {provide: 'resolveSix', useClass: ResolveSix}, {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', - fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => { const fixture = createRoot(router, RootCmpWithTwoOutlets); router.resetConfig([{ @@ -1025,6 +1026,57 @@ describe('Integration', () => { expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3}); }))); + + describe('should run resolvers for the same route concurrently', () => { + let log: string[]; + let observer: Observer; + + beforeEach(() => { + log = []; + TestBed.configureTestingModule({ + providers: [ + { + provide: 'resolver1', + useValue: () => { + const obs$ = new Observable((obs: Observer) => { + 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', () => {