refactor(router): refactor and simplify router RxJS chains (#40290)

Refactor and simplifiy RxJS usage in the router package
in order to reduce its size and increase performance.

PR Close #40290
This commit is contained in:
Martin Sikora 2020-12-28 14:41:06 +01:00 committed by atscott
parent 4a7a649852
commit 9105005192
11 changed files with 70 additions and 105 deletions

View File

@ -66,4 +66,4 @@
} }
} }
} }
} }

View File

@ -1238,12 +1238,6 @@
{ {
"name": "findPath" "name": "findPath"
}, },
{
"name": "fireActivationStart"
},
{
"name": "fireChildActivationStart"
},
{ {
"name": "first" "name": "first"
}, },
@ -1832,12 +1826,6 @@
{ {
"name": "routerNgProbeToken" "name": "routerNgProbeToken"
}, },
{
"name": "runCanActivate"
},
{
"name": "runCanActivateChild"
},
{ {
"name": "rxSubscriber" "name": "rxSubscriber"
}, },

View File

@ -105,12 +105,10 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
/** @nodoc */ /** @nodoc */
ngAfterContentInit(): void { ngAfterContentInit(): void {
// `of(null)` is used to force subscribe body to execute once immediately (like `startWith`). // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
from([this.links.changes, this.linksWithHrefs.changes, of(null)]) of(this.links.changes, this.linksWithHrefs.changes, of(null)).pipe(mergeAll()).subscribe(_ => {
.pipe(mergeAll()) this.update();
.subscribe(_ => { this.subscribeToEachLinkOnChanges();
this.update(); });
this.subscribeToEachLinkOnChanges();
});
} }
private subscribeToEachLinkOnChanges() { private subscribeToEachLinkOnChanges() {

View File

@ -7,7 +7,7 @@
*/ */
import {Injector} from '@angular/core'; import {Injector} from '@angular/core';
import {MonoTypeOperatorFunction, Observable} from 'rxjs'; import {MonoTypeOperatorFunction} from 'rxjs';
import {map, switchMap} from 'rxjs/operators'; import {map, switchMap} from 'rxjs/operators';
import {applyRedirects as applyRedirectsFn} from '../apply_redirects'; import {applyRedirects as applyRedirectsFn} from '../apply_redirects';
@ -19,9 +19,7 @@ import {UrlSerializer} from '../url_tree';
export function applyRedirects( export function applyRedirects(
moduleInjector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer, moduleInjector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
config: Routes): MonoTypeOperatorFunction<NavigationTransition> { config: Routes): MonoTypeOperatorFunction<NavigationTransition> {
return function(source: Observable<NavigationTransition>) { return switchMap(
return source.pipe(switchMap( t => applyRedirectsFn(moduleInjector, configLoader, urlSerializer, t.extractedUrl, config)
t => applyRedirectsFn(moduleInjector, configLoader, urlSerializer, t.extractedUrl, config) .pipe(map(urlAfterRedirects => ({...t, urlAfterRedirects}))));
.pipe(map(urlAfterRedirects => ({...t, urlAfterRedirects})))));
};
} }

View File

@ -7,8 +7,8 @@
*/ */
import {Injector} from '@angular/core'; import {Injector} from '@angular/core';
import {defer, from, MonoTypeOperatorFunction, Observable, of} from 'rxjs'; import {concat, defer, from, MonoTypeOperatorFunction, Observable, of} from 'rxjs';
import {concatAll, concatMap, first, map, mergeMap} from 'rxjs/operators'; import {concatMap, first, map, mergeMap} from 'rxjs/operators';
import {ActivationStart, ChildActivationStart, Event} from '../events'; import {ActivationStart, ChildActivationStart, Event} from '../events';
import {CanActivateChildFn, CanActivateFn, CanDeactivateFn} from '../interfaces'; import {CanActivateChildFn, CanActivateFn, CanDeactivateFn} from '../interfaces';
@ -23,25 +23,23 @@ import {prioritizedGuardValue} from './prioritized_guard_value';
export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event) => void): export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event) => void):
MonoTypeOperatorFunction<NavigationTransition> { MonoTypeOperatorFunction<NavigationTransition> {
return function(source: Observable<NavigationTransition>) { return mergeMap(t => {
return source.pipe(mergeMap(t => { const {targetSnapshot, currentSnapshot, guards: {canActivateChecks, canDeactivateChecks}} = t;
const {targetSnapshot, currentSnapshot, guards: {canActivateChecks, canDeactivateChecks}} = t; if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) { return of({...t, guardsResult: true});
return of({...t, guardsResult: true}); }
}
return runCanDeactivateChecks( return runCanDeactivateChecks(
canDeactivateChecks, targetSnapshot!, currentSnapshot, moduleInjector) canDeactivateChecks, targetSnapshot!, currentSnapshot, moduleInjector)
.pipe( .pipe(
mergeMap(canDeactivate => { mergeMap(canDeactivate => {
return canDeactivate && isBoolean(canDeactivate) ? return canDeactivate && isBoolean(canDeactivate) ?
runCanActivateChecks( runCanActivateChecks(
targetSnapshot!, canActivateChecks, moduleInjector, forwardEvent) : targetSnapshot!, canActivateChecks, moduleInjector, forwardEvent) :
of(canDeactivate); of(canDeactivate);
}), }),
map(guardsResult => ({...t, guardsResult}))); map(guardsResult => ({...t, guardsResult})));
})); });
};
} }
function runCanDeactivateChecks( function runCanDeactivateChecks(
@ -61,15 +59,11 @@ function runCanActivateChecks(
forwardEvent?: (evt: Event) => void) { forwardEvent?: (evt: Event) => void) {
return from(checks).pipe( return from(checks).pipe(
concatMap((check: CanActivate) => { concatMap((check: CanActivate) => {
return from([ return concat(
fireChildActivationStart(check.route.parent, forwardEvent), fireChildActivationStart(check.route.parent, forwardEvent),
fireActivationStart(check.route, forwardEvent), fireActivationStart(check.route, forwardEvent),
runCanActivateChild(futureSnapshot, check.path, moduleInjector), runCanActivateChild(futureSnapshot, check.path, moduleInjector),
runCanActivate(futureSnapshot, check.route, moduleInjector) runCanActivate(futureSnapshot, check.route, moduleInjector));
])
.pipe(concatAll(), first(result => {
return result !== true;
}, true as boolean | UrlTree));
}), }),
first(result => { first(result => {
return result !== true; return result !== true;

View File

@ -18,8 +18,7 @@ declare type INTERIM_VALUES = typeof INITIAL_VALUE | boolean | UrlTree;
export function prioritizedGuardValue(): export function prioritizedGuardValue():
OperatorFunction<Observable<boolean|UrlTree>[], boolean|UrlTree> { OperatorFunction<Observable<boolean|UrlTree>[], boolean|UrlTree> {
return switchMap(obs => { return switchMap(obs => {
return combineLatest( return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE as INTERIM_VALUES))))
...obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE as INTERIM_VALUES))))
.pipe( .pipe(
scan( scan(
(acc: INTERIM_VALUES, list: INTERIM_VALUES[]) => { (acc: INTERIM_VALUES, list: INTERIM_VALUES[]) => {

View File

@ -7,7 +7,7 @@
*/ */
import {Type} from '@angular/core'; import {Type} from '@angular/core';
import {MonoTypeOperatorFunction, Observable} from 'rxjs'; import {MonoTypeOperatorFunction} from 'rxjs';
import {map, mergeMap} from 'rxjs/operators'; import {map, mergeMap} from 'rxjs/operators';
import {Route} from '../config'; import {Route} from '../config';
@ -19,11 +19,9 @@ export function recognize(
rootComponentType: Type<any>|null, config: Route[], serializer: (url: UrlTree) => string, rootComponentType: Type<any>|null, config: Route[], serializer: (url: UrlTree) => string,
paramsInheritanceStrategy: 'emptyOnly'|'always', paramsInheritanceStrategy: 'emptyOnly'|'always',
relativeLinkResolution: 'legacy'|'corrected'): MonoTypeOperatorFunction<NavigationTransition> { relativeLinkResolution: 'legacy'|'corrected'): MonoTypeOperatorFunction<NavigationTransition> {
return function(source: Observable<NavigationTransition>) { return mergeMap(
return source.pipe(mergeMap( t => recognizeFn(
t => recognizeFn( rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects),
rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects), paramsInheritanceStrategy, relativeLinkResolution)
paramsInheritanceStrategy, relativeLinkResolution) .pipe(map(targetSnapshot => ({...t, targetSnapshot}))));
.pipe(map(targetSnapshot => ({...t, targetSnapshot})))));
};
} }

View File

@ -19,25 +19,23 @@ import {getToken} from '../utils/preactivation';
export function resolveData( export function resolveData(
paramsInheritanceStrategy: 'emptyOnly'|'always', paramsInheritanceStrategy: 'emptyOnly'|'always',
moduleInjector: Injector): MonoTypeOperatorFunction<NavigationTransition> { moduleInjector: Injector): MonoTypeOperatorFunction<NavigationTransition> {
return function(source: Observable<NavigationTransition>) { return mergeMap(t => {
return source.pipe(mergeMap(t => { const {targetSnapshot, guards: {canActivateChecks}} = t;
const {targetSnapshot, guards: {canActivateChecks}} = t;
if (!canActivateChecks.length) { if (!canActivateChecks.length) {
return of(t); return of(t);
} }
let canActivateChecksResolved = 0; let canActivateChecksResolved = 0;
return from(canActivateChecks) return from(canActivateChecks)
.pipe( .pipe(
concatMap( concatMap(
check => runResolve( check => runResolve(
check.route, targetSnapshot!, paramsInheritanceStrategy, moduleInjector)), check.route, targetSnapshot!, paramsInheritanceStrategy, moduleInjector)),
tap(() => canActivateChecksResolved++), tap(() => canActivateChecksResolved++),
takeLast(1), takeLast(1),
mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY), mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY),
); );
})); });
};
} }
function runResolve( function runResolve(

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {from, MonoTypeOperatorFunction, ObservableInput} from 'rxjs'; import {from, MonoTypeOperatorFunction, ObservableInput, of} from 'rxjs';
import {map, switchMap} from 'rxjs/operators'; import {map, switchMap} from 'rxjs/operators';
/** /**
@ -17,13 +17,11 @@ import {map, switchMap} from 'rxjs/operators';
*/ */
export function switchTap<T>(next: (x: T) => void|ObservableInput<any>): export function switchTap<T>(next: (x: T) => void|ObservableInput<any>):
MonoTypeOperatorFunction<T> { MonoTypeOperatorFunction<T> {
return function(source) { return switchMap(v => {
return source.pipe(switchMap(v => { const nextResult = next(v);
const nextResult = next(v); if (nextResult) {
if (nextResult) { return from(nextResult).pipe(map(() => v));
return from(nextResult).pipe(map(() => v)); }
} return of(v);
return from([v]); });
}));
};
} }

View File

@ -573,12 +573,11 @@ export class Router {
if (transition !== this.transitions.getValue()) { if (transition !== this.transitions.getValue()) {
return EMPTY; return EMPTY;
} }
return [t];
}),
// This delay is required to match old behavior that forced navigation // This delay is required to match old behavior that forced
// to always be async // navigation to always be async
switchMap(t => Promise.resolve(t)), return Promise.resolve(t);
}),
// ApplyRedirects // ApplyRedirects
applyRedirects( applyRedirects(
@ -609,10 +608,8 @@ export class Router {
} }
this.browserUrlTree = t.urlAfterRedirects; this.browserUrlTree = t.urlAfterRedirects;
} }
}),
// Fire RoutesRecognized // Fire RoutesRecognized
tap(t => {
const routesRecognized = new RoutesRecognized( const routesRecognized = new RoutesRecognized(
t.id, this.serializeUrl(t.extractedUrl), t.id, this.serializeUrl(t.extractedUrl),
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!); this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!);
@ -692,9 +689,7 @@ export class Router {
error.url = t.guardsResult; error.url = t.guardsResult;
throw error; throw error;
} }
}),
tap(t => {
const guardsEnd = new GuardsCheckEnd( const guardsEnd = new GuardsCheckEnd(
t.id, this.serializeUrl(t.extractedUrl), t.id, this.serializeUrl(t.extractedUrl),
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!, this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!,
@ -875,7 +870,7 @@ export class Router {
replaceUrl: this.urlUpdateStrategy === 'eager' replaceUrl: this.urlUpdateStrategy === 'eager'
}; };
return this.scheduleNavigation( this.scheduleNavigation(
mergedTree, 'imperative', null, extras, mergedTree, 'imperative', null, extras,
{resolve: t.resolve, reject: t.reject, promise: t.promise}); {resolve: t.resolve, reject: t.reject, promise: t.promise});
}, 0); }, 0);

View File

@ -8,9 +8,8 @@
import {ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core'; import {ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {from, Observable, of} from 'rxjs'; import {from, Observable, of} from 'rxjs';
import {concatAll, last as lastValue, map} from 'rxjs/operators';
import {Params, PRIMARY_OUTLET} from '../shared'; import {Params} from '../shared';
export function shallowEqualArrays(a: any[], b: any[]): boolean { export function shallowEqualArrays(a: any[], b: any[]): boolean {
if (a.length !== b.length) return false; if (a.length !== b.length) return false;