Andrew Scott e43f7e26fe fix(router): apply redirects should match named outlets with empty path parents (#40029)
There are two parts to this commit:
1. Revert the changes from #38379. This change had an incomplete view of
how things worked and also diverged the implementations of
`applyRedirects` and `recognize` even more.
2. Apply the fixes from the `recognize` algorithm to ensure that named
outlets with empty path parents can be matched. This change also passes
all the tests that were added in #38379 with the added benefit of being
a more complete fix that stays in-line with the `recognize` algorithm.
This was made possible by using the same approach for `split` by
always creating segments for empty path matches (previously, this was
only done in `applyRedirects` if there was a `redirectTo` value). At the
end of the expansions, we need to squash all empty segments so that
serializing the final `UrlTree` returns the same result as before.

Fixes #39952
Fixes #10726
Closes #30410

PR Close #40029
2021-01-05 12:43:47 -08:00

100 lines
2.8 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {concatAll, last as lastValue, map} from 'rxjs/operators';
import {Params, PRIMARY_OUTLET} from '../shared';
export function shallowEqualArrays(a: any[], b: any[]): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; ++i) {
if (!shallowEqual(a[i], b[i])) return false;
}
return true;
}
export function shallowEqual(a: Params, b: Params): boolean {
// Casting Object.keys return values to include `undefined` as there are some cases
// in IE 11 where this can happen. Cannot provide a test because the behavior only
// exists in certain circumstances in IE 11, therefore doing this cast ensures the
// logic is correct for when this edge case is hit.
const k1 = Object.keys(a) as string[] | undefined;
const k2 = Object.keys(b) as string[] | undefined;
if (!k1 || !k2 || k1.length != k2.length) {
return false;
}
let key: string;
for (let i = 0; i < k1.length; i++) {
key = k1[i];
if (!equalArraysOrString(a[key], b[key])) {
return false;
}
}
return true;
}
/**
* Test equality for arrays of strings or a string.
*/
export function equalArraysOrString(a: string|string[], b: string|string[]) {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
const aSorted = [...a].sort();
const bSorted = [...b].sort();
return aSorted.every((val, index) => bSorted[index] === val);
} else {
return a === b;
}
}
/**
* Flattens single-level nested arrays.
*/
export function flatten<T>(arr: T[][]): T[] {
return Array.prototype.concat.apply([], arr);
}
/**
* Return the last element of an array.
*/
export function last<T>(a: T[]): T|null {
return a.length > 0 ? a[a.length - 1] : null;
}
/**
* Verifys all booleans in an array are `true`.
*/
export function and(bools: boolean[]): boolean {
return !bools.some(v => !v);
}
export function forEach<K, V>(map: {[key: string]: V}, callback: (v: V, k: string) => void): void {
for (const prop in map) {
if (map.hasOwnProperty(prop)) {
callback(map[prop], prop);
}
}
}
export function wrapIntoObservable<T>(value: T|Promise<T>|Observable<T>): Observable<T> {
if (isObservable(value)) {
return value;
}
if (isPromise(value)) {
// Use `Promise.resolve()` to wrap promise-like instances.
// Required ie when a Resolver returns a AngularJS `$q` promise to correctly trigger the
// change detection.
return from(Promise.resolve(value));
}
return of(value);
}