refactor(router): Simplify the prioritizeGuardValue logic (#46745)

The existing logic does something similar but in a more roundabout way.
It reads _the whole array_. If it encounters a pending value, it ignores
the remaining ones. If it hasn't encountered a pending value by the time
it hits false/UrlTree, it returns that result.

The new logic is the same, but reverses what we're looking for. Instead
of processing the whole array, we stop when we encounter an initial
value. When we encounter one that isn't `true`, that gets returned. If
we get to the end and everything was `true`, return `true`.

PR Close #46745
This commit is contained in:
Andrew Scott
2022-07-07 21:28:59 -07:00
committed by Jessica Janiuk
parent e59c7047de
commit cee207ea11
@@ -7,48 +7,43 @@
*/
import {combineLatest, Observable, OperatorFunction} from 'rxjs';
import {filter, map, scan, startWith, switchMap, take} from 'rxjs/operators';
import {filter, map, startWith, switchMap, take} from 'rxjs/operators';
import {UrlTree} from '../url_tree';
import {isUrlTree} from '../utils/type_guards';
const INITIAL_VALUE = Symbol('INITIAL_VALUE');
const INITIAL_VALUE = Symbol();
declare type INTERIM_VALUES = typeof INITIAL_VALUE | boolean | UrlTree;
/**
* Takes an array of observables and returns the result of the first to emit something other than
* `true`.
*
* If all Observables emit `true`, then this operator emits true.
* Also note that only the first value of each observable is used.
*/
export function prioritizedGuardValue():
OperatorFunction<Observable<boolean|UrlTree>[], boolean|UrlTree> {
return switchMap(obs => {
return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE as INTERIM_VALUES))))
.pipe(
scan(
(acc: INTERIM_VALUES, list: INTERIM_VALUES[]) => {
let isPending = false;
return list.reduce((innerAcc, val, i: number) => {
if (innerAcc !== INITIAL_VALUE) return innerAcc;
// Toggle pending flag if any values haven't been set yet
if (val === INITIAL_VALUE) isPending = true;
// Any other return values are only valid if we haven't yet hit a pending
// call. This guarantees that in the case of a guard at the bottom of the
// tree that returns a redirect, we will wait for the higher priority
// guard at the top to finish before performing the redirect.
if (!isPending) {
// Early return when we hit a `false` value as that should always
// cancel navigation
if (val === false) return val;
if (i === list.length - 1 || isUrlTree(val)) {
return val;
}
}
return innerAcc;
}, acc);
},
INITIAL_VALUE),
filter(item => item !== INITIAL_VALUE),
map(item => isUrlTree(item) ? item : item === true), //
take(1)) as Observable<boolean|UrlTree>;
.pipe(
map((results: INTERIM_VALUES[]) => {
for (const result of results) {
if (result === true) {
// If result is true, check the next one
continue;
} else if (result === INITIAL_VALUE) {
// If guard has not finished, we need to stop processing.
return INITIAL_VALUE;
} else {
// Result finished and was not true. Return the result.
return result;
}
}
// Everything resolved to true. Return true.
return true;
}),
filter((item): item is boolean|UrlTree => item !== INITIAL_VALUE),
take(1),
);
});
}