perf(router): apply prioritizedGuardValue operator to optimize CanLoad guards (#37523)
CanLoad guards are processed in asynchronous manner with the following rules: * If all guards return `true`, operator returns `true`; * `false` and `UrlTree` values wait for higher priority guards to resolve; * Highest priority `false` or `UrlTree` value will be returned. `prioritizedGuardValue` uses `combineLatest` which in order subscribes to each Observable immediately (not waiting when previous one completes that `concatAll` do). So it makes some advantages in order to run them concurrently. Respectively, a time to resolve all guards will be reduced. PR Close #37523
This commit is contained in:

committed by
Alex Rickabaugh

parent
a5ffca0576
commit
d7dd2959c8
@ -14,7 +14,7 @@ import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DefaultUrlSerializer, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, Navigation, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ParamMap, Params, PreloadAllModules, PreloadingStrategy, PRIMARY_OUTLET, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouteReuseStrategy, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
|
||||
import {EMPTY, Observable, Observer, of, Subscription} from 'rxjs';
|
||||
import {filter, first, map, tap} from 'rxjs/operators';
|
||||
import {delay, filter, first, map, mapTo, tap} from 'rxjs/operators';
|
||||
|
||||
import {forEach} from '../src/utils/collection';
|
||||
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
|
||||
@ -3998,6 +3998,112 @@ describe('Integration', () => {
|
||||
})));
|
||||
});
|
||||
|
||||
describe('should run CanLoad guards concurrently', () => {
|
||||
function delayObservable(delayMs: number): Observable<boolean> {
|
||||
return of(delayMs).pipe(delay(delayMs), mapTo(true));
|
||||
}
|
||||
|
||||
@NgModule()
|
||||
class LoadedModule {
|
||||
}
|
||||
|
||||
let log: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
log = [];
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: 'guard1',
|
||||
useValue: () => {
|
||||
return delayObservable(5).pipe(tap({next: () => log.push('guard1')}));
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'guard2',
|
||||
useValue: () => {
|
||||
return delayObservable(0).pipe(tap({next: () => log.push('guard2')}));
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'returnFalse',
|
||||
useValue: () => {
|
||||
log.push('returnFalse');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'returnUrlTree',
|
||||
useFactory: (router: Router) => () => {
|
||||
return delayObservable(15).pipe(
|
||||
mapTo(router.parseUrl('/redirected')),
|
||||
tap({next: () => log.push('returnUrlTree')}));
|
||||
},
|
||||
deps: [Router]
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should wait for higher priority guards to be resolved',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader) => {
|
||||
loader.stubbedModules = {expected: LoadedModule};
|
||||
|
||||
router.resetConfig(
|
||||
[{path: 'lazy', canLoad: ['guard1', 'guard2'], loadChildren: 'expected'}]);
|
||||
|
||||
router.navigateByUrl('/lazy');
|
||||
tick(5);
|
||||
|
||||
expect(log.length).toEqual(2);
|
||||
expect(log).toEqual(['guard2', 'guard1']);
|
||||
})));
|
||||
|
||||
it('should redirect with UrlTree if higher priority guards have resolved',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader, Location],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader, location: Location) => {
|
||||
loader.stubbedModules = {expected: LoadedModule};
|
||||
|
||||
router.resetConfig([
|
||||
{
|
||||
path: 'lazy',
|
||||
canLoad: ['returnUrlTree', 'guard1', 'guard2'],
|
||||
loadChildren: 'expected'
|
||||
},
|
||||
{path: 'redirected', component: SimpleCmp}
|
||||
]);
|
||||
|
||||
router.navigateByUrl('/lazy');
|
||||
tick(15);
|
||||
|
||||
expect(log.length).toEqual(3);
|
||||
expect(log).toEqual(['guard2', 'guard1', 'returnUrlTree']);
|
||||
expect(location.path()).toEqual('/redirected');
|
||||
})));
|
||||
|
||||
it('should redirect with UrlTree if UrlTree is lower priority',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader, Location],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader, location: Location) => {
|
||||
loader.stubbedModules = {expected: LoadedModule};
|
||||
|
||||
router.resetConfig([
|
||||
{path: 'lazy', canLoad: ['guard1', 'returnUrlTree'], loadChildren: 'expected'},
|
||||
{path: 'redirected', component: SimpleCmp}
|
||||
]);
|
||||
|
||||
router.navigateByUrl('/lazy');
|
||||
tick(15);
|
||||
|
||||
expect(log.length).toEqual(2);
|
||||
expect(log).toEqual(['guard1', 'returnUrlTree']);
|
||||
expect(location.path()).toEqual('/redirected');
|
||||
})));
|
||||
});
|
||||
|
||||
describe('order', () => {
|
||||
class Logger {
|
||||
logs: string[] = [];
|
||||
|
Reference in New Issue
Block a user