mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4674 lines
160 KiB
4674 lines
160 KiB
/**
|
|
* @license Angular v21.1.1
|
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
* License: MIT
|
|
*/
|
|
|
|
import { DOCUMENT, Location } from '@angular/common';
|
|
import * as i0 from '@angular/core';
|
|
import { ɵisPromise as _isPromise, computed, ɵRuntimeError as _RuntimeError, Injectable, InjectionToken, EventEmitter, input, inject, ViewContainerRef, ChangeDetectorRef, Directive, Input, Output, reflectComponentType, Component, runInInjectionContext, ɵisInjectable as _isInjectable, ɵisNgModule as _isNgModule, isStandalone, createEnvironmentInjector, Compiler, NgModuleFactory, ɵresolveComponentResources as _resolveComponentResources, afterNextRender, signal, EnvironmentInjector, DestroyRef, untracked, ɵConsole as _Console, ɵPendingTasksInternal as _PendingTasksInternal, ɵINTERNAL_APPLICATION_ERROR_HANDLER as _INTERNAL_APPLICATION_ERROR_HANDLER, ɵformatRuntimeError as _formatRuntimeError } from '@angular/core';
|
|
import { isObservable, from, of, BehaviorSubject, combineLatest, EmptyError, Observable, concat, defer, pipe, EMPTY, throwError, Subject, Subscription } from 'rxjs';
|
|
import { first, map, switchMap, take, startWith, filter, takeUntil, mergeMap, concatMap, tap, takeLast, catchError, finalize } from 'rxjs/operators';
|
|
import * as i1 from '@angular/platform-browser';
|
|
|
|
const PRIMARY_OUTLET = 'primary';
|
|
const RouteTitleKey = /* @__PURE__ */Symbol('RouteTitle');
|
|
class ParamsAsMap {
|
|
params;
|
|
constructor(params) {
|
|
this.params = params || {};
|
|
}
|
|
has(name) {
|
|
return Object.prototype.hasOwnProperty.call(this.params, name);
|
|
}
|
|
get(name) {
|
|
if (this.has(name)) {
|
|
const v = this.params[name];
|
|
return Array.isArray(v) ? v[0] : v;
|
|
}
|
|
return null;
|
|
}
|
|
getAll(name) {
|
|
if (this.has(name)) {
|
|
const v = this.params[name];
|
|
return Array.isArray(v) ? v : [v];
|
|
}
|
|
return [];
|
|
}
|
|
get keys() {
|
|
return Object.keys(this.params);
|
|
}
|
|
}
|
|
function convertToParamMap(params) {
|
|
return new ParamsAsMap(params);
|
|
}
|
|
function matchParts(routeParts, urlSegments, posParams) {
|
|
for (let i = 0; i < routeParts.length; i++) {
|
|
const part = routeParts[i];
|
|
const segment = urlSegments[i];
|
|
const isParameter = part[0] === ':';
|
|
if (isParameter) {
|
|
posParams[part.substring(1)] = segment;
|
|
} else if (part !== segment.path) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function defaultUrlMatcher(segments, segmentGroup, route) {
|
|
const parts = route.path.split('/');
|
|
const wildcardIndex = parts.indexOf('**');
|
|
if (wildcardIndex === -1) {
|
|
if (parts.length > segments.length) {
|
|
return null;
|
|
}
|
|
if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || parts.length < segments.length)) {
|
|
return null;
|
|
}
|
|
const posParams = {};
|
|
const consumed = segments.slice(0, parts.length);
|
|
if (!matchParts(parts, consumed, posParams)) {
|
|
return null;
|
|
}
|
|
return {
|
|
consumed,
|
|
posParams
|
|
};
|
|
}
|
|
if (wildcardIndex !== parts.lastIndexOf('**')) {
|
|
return null;
|
|
}
|
|
const pre = parts.slice(0, wildcardIndex);
|
|
const post = parts.slice(wildcardIndex + 1);
|
|
if (pre.length + post.length > segments.length) {
|
|
return null;
|
|
}
|
|
if (route.pathMatch === 'full' && segmentGroup.hasChildren() && route.path !== '**') {
|
|
return null;
|
|
}
|
|
const posParams = {};
|
|
if (!matchParts(pre, segments.slice(0, pre.length), posParams)) {
|
|
return null;
|
|
}
|
|
if (!matchParts(post, segments.slice(segments.length - post.length), posParams)) {
|
|
return null;
|
|
}
|
|
return {
|
|
consumed: segments,
|
|
posParams
|
|
};
|
|
}
|
|
|
|
function firstValueFrom(source) {
|
|
return new Promise((resolve, reject) => {
|
|
source.pipe(first()).subscribe({
|
|
next: value => resolve(value),
|
|
error: err => reject(err)
|
|
});
|
|
});
|
|
}
|
|
|
|
function shallowEqualArrays(a, b) {
|
|
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;
|
|
}
|
|
function shallowEqual(a, b) {
|
|
const k1 = a ? getDataKeys(a) : undefined;
|
|
const k2 = b ? getDataKeys(b) : undefined;
|
|
if (!k1 || !k2 || k1.length != k2.length) {
|
|
return false;
|
|
}
|
|
let key;
|
|
for (let i = 0; i < k1.length; i++) {
|
|
key = k1[i];
|
|
if (!equalArraysOrString(a[key], b[key])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function getDataKeys(obj) {
|
|
return [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
|
|
}
|
|
function equalArraysOrString(a, b) {
|
|
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;
|
|
}
|
|
}
|
|
function last(a) {
|
|
return a.length > 0 ? a[a.length - 1] : null;
|
|
}
|
|
function wrapIntoObservable(value) {
|
|
if (isObservable(value)) {
|
|
return value;
|
|
}
|
|
if (_isPromise(value)) {
|
|
return from(Promise.resolve(value));
|
|
}
|
|
return of(value);
|
|
}
|
|
function wrapIntoPromise(value) {
|
|
if (isObservable(value)) {
|
|
return firstValueFrom(value);
|
|
}
|
|
return Promise.resolve(value);
|
|
}
|
|
|
|
const pathCompareMap = {
|
|
'exact': equalSegmentGroups,
|
|
'subset': containsSegmentGroup
|
|
};
|
|
const paramCompareMap = {
|
|
'exact': equalParams,
|
|
'subset': containsParams,
|
|
'ignored': () => true
|
|
};
|
|
function isActive(url, router, matchOptions) {
|
|
const urlTree = url instanceof UrlTree ? url : router.parseUrl(url);
|
|
return computed(() => containsTree(router.lastSuccessfulNavigation()?.finalUrl ?? new UrlTree(), urlTree, matchOptions));
|
|
}
|
|
function containsTree(container, containee, options) {
|
|
return pathCompareMap[options.paths](container.root, containee.root, options.matrixParams) && paramCompareMap[options.queryParams](container.queryParams, containee.queryParams) && !(options.fragment === 'exact' && container.fragment !== containee.fragment);
|
|
}
|
|
function equalParams(container, containee) {
|
|
return shallowEqual(container, containee);
|
|
}
|
|
function equalSegmentGroups(container, containee, matrixParams) {
|
|
if (!equalPath(container.segments, containee.segments)) return false;
|
|
if (!matrixParamsMatch(container.segments, containee.segments, matrixParams)) {
|
|
return false;
|
|
}
|
|
if (container.numberOfChildren !== containee.numberOfChildren) return false;
|
|
for (const c in containee.children) {
|
|
if (!container.children[c]) return false;
|
|
if (!equalSegmentGroups(container.children[c], containee.children[c], matrixParams)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
function containsParams(container, containee) {
|
|
return Object.keys(containee).length <= Object.keys(container).length && Object.keys(containee).every(key => equalArraysOrString(container[key], containee[key]));
|
|
}
|
|
function containsSegmentGroup(container, containee, matrixParams) {
|
|
return containsSegmentGroupHelper(container, containee, containee.segments, matrixParams);
|
|
}
|
|
function containsSegmentGroupHelper(container, containee, containeePaths, matrixParams) {
|
|
if (container.segments.length > containeePaths.length) {
|
|
const current = container.segments.slice(0, containeePaths.length);
|
|
if (!equalPath(current, containeePaths)) return false;
|
|
if (containee.hasChildren()) return false;
|
|
if (!matrixParamsMatch(current, containeePaths, matrixParams)) return false;
|
|
return true;
|
|
} else if (container.segments.length === containeePaths.length) {
|
|
if (!equalPath(container.segments, containeePaths)) return false;
|
|
if (!matrixParamsMatch(container.segments, containeePaths, matrixParams)) return false;
|
|
for (const c in containee.children) {
|
|
if (!container.children[c]) return false;
|
|
if (!containsSegmentGroup(container.children[c], containee.children[c], matrixParams)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
const current = containeePaths.slice(0, container.segments.length);
|
|
const next = containeePaths.slice(container.segments.length);
|
|
if (!equalPath(container.segments, current)) return false;
|
|
if (!matrixParamsMatch(container.segments, current, matrixParams)) return false;
|
|
if (!container.children[PRIMARY_OUTLET]) return false;
|
|
return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next, matrixParams);
|
|
}
|
|
}
|
|
function matrixParamsMatch(containerPaths, containeePaths, options) {
|
|
return containeePaths.every((containeeSegment, i) => {
|
|
return paramCompareMap[options](containerPaths[i].parameters, containeeSegment.parameters);
|
|
});
|
|
}
|
|
class UrlTree {
|
|
root;
|
|
queryParams;
|
|
fragment;
|
|
_queryParamMap;
|
|
constructor(root = new UrlSegmentGroup([], {}), queryParams = {}, fragment = null) {
|
|
this.root = root;
|
|
this.queryParams = queryParams;
|
|
this.fragment = fragment;
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
if (root.segments.length > 0) {
|
|
throw new _RuntimeError(4015, 'The root `UrlSegmentGroup` should not contain `segments`. ' + 'Instead, these segments belong in the `children` so they can be associated with a named outlet.');
|
|
}
|
|
}
|
|
}
|
|
get queryParamMap() {
|
|
this._queryParamMap ??= convertToParamMap(this.queryParams);
|
|
return this._queryParamMap;
|
|
}
|
|
toString() {
|
|
return DEFAULT_SERIALIZER.serialize(this);
|
|
}
|
|
}
|
|
class UrlSegmentGroup {
|
|
segments;
|
|
children;
|
|
parent = null;
|
|
constructor(segments, children) {
|
|
this.segments = segments;
|
|
this.children = children;
|
|
Object.values(children).forEach(v => v.parent = this);
|
|
}
|
|
hasChildren() {
|
|
return this.numberOfChildren > 0;
|
|
}
|
|
get numberOfChildren() {
|
|
return Object.keys(this.children).length;
|
|
}
|
|
toString() {
|
|
return serializePaths(this);
|
|
}
|
|
}
|
|
class UrlSegment {
|
|
path;
|
|
parameters;
|
|
_parameterMap;
|
|
constructor(path, parameters) {
|
|
this.path = path;
|
|
this.parameters = parameters;
|
|
}
|
|
get parameterMap() {
|
|
this._parameterMap ??= convertToParamMap(this.parameters);
|
|
return this._parameterMap;
|
|
}
|
|
toString() {
|
|
return serializePath(this);
|
|
}
|
|
}
|
|
function equalSegments(as, bs) {
|
|
return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters));
|
|
}
|
|
function equalPath(as, bs) {
|
|
if (as.length !== bs.length) return false;
|
|
return as.every((a, i) => a.path === bs[i].path);
|
|
}
|
|
function mapChildrenIntoArray(segment, fn) {
|
|
let res = [];
|
|
Object.entries(segment.children).forEach(([childOutlet, child]) => {
|
|
if (childOutlet === PRIMARY_OUTLET) {
|
|
res = res.concat(fn(child, childOutlet));
|
|
}
|
|
});
|
|
Object.entries(segment.children).forEach(([childOutlet, child]) => {
|
|
if (childOutlet !== PRIMARY_OUTLET) {
|
|
res = res.concat(fn(child, childOutlet));
|
|
}
|
|
});
|
|
return res;
|
|
}
|
|
class UrlSerializer {
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlSerializer,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlSerializer,
|
|
providedIn: 'root',
|
|
useFactory: () => new DefaultUrlSerializer()
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlSerializer,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root',
|
|
useFactory: () => new DefaultUrlSerializer()
|
|
}]
|
|
}]
|
|
});
|
|
class DefaultUrlSerializer {
|
|
parse(url) {
|
|
const p = new UrlParser(url);
|
|
return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
|
|
}
|
|
serialize(tree) {
|
|
const segment = `/${serializeSegment(tree.root, true)}`;
|
|
const query = serializeQueryParams(tree.queryParams);
|
|
const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment)}` : '';
|
|
return `${segment}${query}${fragment}`;
|
|
}
|
|
}
|
|
const DEFAULT_SERIALIZER = new DefaultUrlSerializer();
|
|
function serializePaths(segment) {
|
|
return segment.segments.map(p => serializePath(p)).join('/');
|
|
}
|
|
function serializeSegment(segment, root) {
|
|
if (!segment.hasChildren()) {
|
|
return serializePaths(segment);
|
|
}
|
|
if (root) {
|
|
const primary = segment.children[PRIMARY_OUTLET] ? serializeSegment(segment.children[PRIMARY_OUTLET], false) : '';
|
|
const children = [];
|
|
Object.entries(segment.children).forEach(([k, v]) => {
|
|
if (k !== PRIMARY_OUTLET) {
|
|
children.push(`${k}:${serializeSegment(v, false)}`);
|
|
}
|
|
});
|
|
return children.length > 0 ? `${primary}(${children.join('//')})` : primary;
|
|
} else {
|
|
const children = mapChildrenIntoArray(segment, (v, k) => {
|
|
if (k === PRIMARY_OUTLET) {
|
|
return [serializeSegment(segment.children[PRIMARY_OUTLET], false)];
|
|
}
|
|
return [`${k}:${serializeSegment(v, false)}`];
|
|
});
|
|
if (Object.keys(segment.children).length === 1 && segment.children[PRIMARY_OUTLET] != null) {
|
|
return `${serializePaths(segment)}/${children[0]}`;
|
|
}
|
|
return `${serializePaths(segment)}/(${children.join('//')})`;
|
|
}
|
|
}
|
|
function encodeUriString(s) {
|
|
return encodeURIComponent(s).replace(/%40/g, '@').replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',');
|
|
}
|
|
function encodeUriQuery(s) {
|
|
return encodeUriString(s).replace(/%3B/gi, ';');
|
|
}
|
|
function encodeUriFragment(s) {
|
|
return encodeURI(s);
|
|
}
|
|
function encodeUriSegment(s) {
|
|
return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&');
|
|
}
|
|
function decode(s) {
|
|
return decodeURIComponent(s);
|
|
}
|
|
function decodeQuery(s) {
|
|
return decode(s.replace(/\+/g, '%20'));
|
|
}
|
|
function serializePath(path) {
|
|
return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`;
|
|
}
|
|
function serializeMatrixParams(params) {
|
|
return Object.entries(params).map(([key, value]) => `;${encodeUriSegment(key)}=${encodeUriSegment(value)}`).join('');
|
|
}
|
|
function serializeQueryParams(params) {
|
|
const strParams = Object.entries(params).map(([name, value]) => {
|
|
return Array.isArray(value) ? value.map(v => `${encodeUriQuery(name)}=${encodeUriQuery(v)}`).join('&') : `${encodeUriQuery(name)}=${encodeUriQuery(value)}`;
|
|
}).filter(s => s);
|
|
return strParams.length ? `?${strParams.join('&')}` : '';
|
|
}
|
|
const SEGMENT_RE = /^[^\/()?;#]+/;
|
|
function matchSegments(str) {
|
|
const match = str.match(SEGMENT_RE);
|
|
return match ? match[0] : '';
|
|
}
|
|
const MATRIX_PARAM_SEGMENT_RE = /^[^\/()?;=#]+/;
|
|
function matchMatrixKeySegments(str) {
|
|
const match = str.match(MATRIX_PARAM_SEGMENT_RE);
|
|
return match ? match[0] : '';
|
|
}
|
|
const QUERY_PARAM_RE = /^[^=?&#]+/;
|
|
function matchQueryParams(str) {
|
|
const match = str.match(QUERY_PARAM_RE);
|
|
return match ? match[0] : '';
|
|
}
|
|
const QUERY_PARAM_VALUE_RE = /^[^&#]+/;
|
|
function matchUrlQueryParamValue(str) {
|
|
const match = str.match(QUERY_PARAM_VALUE_RE);
|
|
return match ? match[0] : '';
|
|
}
|
|
class UrlParser {
|
|
url;
|
|
remaining;
|
|
constructor(url) {
|
|
this.url = url;
|
|
this.remaining = url;
|
|
}
|
|
parseRootSegment() {
|
|
this.consumeOptional('/');
|
|
if (this.remaining === '' || this.peekStartsWith('?') || this.peekStartsWith('#')) {
|
|
return new UrlSegmentGroup([], {});
|
|
}
|
|
return new UrlSegmentGroup([], this.parseChildren());
|
|
}
|
|
parseQueryParams() {
|
|
const params = {};
|
|
if (this.consumeOptional('?')) {
|
|
do {
|
|
this.parseQueryParam(params);
|
|
} while (this.consumeOptional('&'));
|
|
}
|
|
return params;
|
|
}
|
|
parseFragment() {
|
|
return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null;
|
|
}
|
|
parseChildren() {
|
|
if (this.remaining === '') {
|
|
return {};
|
|
}
|
|
this.consumeOptional('/');
|
|
const segments = [];
|
|
if (!this.peekStartsWith('(')) {
|
|
segments.push(this.parseSegment());
|
|
}
|
|
while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) {
|
|
this.capture('/');
|
|
segments.push(this.parseSegment());
|
|
}
|
|
let children = {};
|
|
if (this.peekStartsWith('/(')) {
|
|
this.capture('/');
|
|
children = this.parseParens(true);
|
|
}
|
|
let res = {};
|
|
if (this.peekStartsWith('(')) {
|
|
res = this.parseParens(false);
|
|
}
|
|
if (segments.length > 0 || Object.keys(children).length > 0) {
|
|
res[PRIMARY_OUTLET] = new UrlSegmentGroup(segments, children);
|
|
}
|
|
return res;
|
|
}
|
|
parseSegment() {
|
|
const path = matchSegments(this.remaining);
|
|
if (path === '' && this.peekStartsWith(';')) {
|
|
throw new _RuntimeError(4009, (typeof ngDevMode === 'undefined' || ngDevMode) && `Empty path url segment cannot have parameters: '${this.remaining}'.`);
|
|
}
|
|
this.capture(path);
|
|
return new UrlSegment(decode(path), this.parseMatrixParams());
|
|
}
|
|
parseMatrixParams() {
|
|
const params = {};
|
|
while (this.consumeOptional(';')) {
|
|
this.parseParam(params);
|
|
}
|
|
return params;
|
|
}
|
|
parseParam(params) {
|
|
const key = matchMatrixKeySegments(this.remaining);
|
|
if (!key) {
|
|
return;
|
|
}
|
|
this.capture(key);
|
|
let value = '';
|
|
if (this.consumeOptional('=')) {
|
|
const valueMatch = matchSegments(this.remaining);
|
|
if (valueMatch) {
|
|
value = valueMatch;
|
|
this.capture(value);
|
|
}
|
|
}
|
|
params[decode(key)] = decode(value);
|
|
}
|
|
parseQueryParam(params) {
|
|
const key = matchQueryParams(this.remaining);
|
|
if (!key) {
|
|
return;
|
|
}
|
|
this.capture(key);
|
|
let value = '';
|
|
if (this.consumeOptional('=')) {
|
|
const valueMatch = matchUrlQueryParamValue(this.remaining);
|
|
if (valueMatch) {
|
|
value = valueMatch;
|
|
this.capture(value);
|
|
}
|
|
}
|
|
const decodedKey = decodeQuery(key);
|
|
const decodedVal = decodeQuery(value);
|
|
if (params.hasOwnProperty(decodedKey)) {
|
|
let currentVal = params[decodedKey];
|
|
if (!Array.isArray(currentVal)) {
|
|
currentVal = [currentVal];
|
|
params[decodedKey] = currentVal;
|
|
}
|
|
currentVal.push(decodedVal);
|
|
} else {
|
|
params[decodedKey] = decodedVal;
|
|
}
|
|
}
|
|
parseParens(allowPrimary) {
|
|
const segments = {};
|
|
this.capture('(');
|
|
while (!this.consumeOptional(')') && this.remaining.length > 0) {
|
|
const path = matchSegments(this.remaining);
|
|
const next = this.remaining[path.length];
|
|
if (next !== '/' && next !== ')' && next !== ';') {
|
|
throw new _RuntimeError(4010, (typeof ngDevMode === 'undefined' || ngDevMode) && `Cannot parse url '${this.url}'`);
|
|
}
|
|
let outletName;
|
|
if (path.indexOf(':') > -1) {
|
|
outletName = path.slice(0, path.indexOf(':'));
|
|
this.capture(outletName);
|
|
this.capture(':');
|
|
} else if (allowPrimary) {
|
|
outletName = PRIMARY_OUTLET;
|
|
}
|
|
const children = this.parseChildren();
|
|
segments[outletName ?? PRIMARY_OUTLET] = Object.keys(children).length === 1 && children[PRIMARY_OUTLET] ? children[PRIMARY_OUTLET] : new UrlSegmentGroup([], children);
|
|
this.consumeOptional('//');
|
|
}
|
|
return segments;
|
|
}
|
|
peekStartsWith(str) {
|
|
return this.remaining.startsWith(str);
|
|
}
|
|
consumeOptional(str) {
|
|
if (this.peekStartsWith(str)) {
|
|
this.remaining = this.remaining.substring(str.length);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
capture(str) {
|
|
if (!this.consumeOptional(str)) {
|
|
throw new _RuntimeError(4011, (typeof ngDevMode === 'undefined' || ngDevMode) && `Expected "${str}".`);
|
|
}
|
|
}
|
|
}
|
|
function createRoot(rootCandidate) {
|
|
return rootCandidate.segments.length > 0 ? new UrlSegmentGroup([], {
|
|
[PRIMARY_OUTLET]: rootCandidate
|
|
}) : rootCandidate;
|
|
}
|
|
function squashSegmentGroup(segmentGroup) {
|
|
const newChildren = {};
|
|
for (const [childOutlet, child] of Object.entries(segmentGroup.children)) {
|
|
const childCandidate = squashSegmentGroup(child);
|
|
if (childOutlet === PRIMARY_OUTLET && childCandidate.segments.length === 0 && childCandidate.hasChildren()) {
|
|
for (const [grandChildOutlet, grandChild] of Object.entries(childCandidate.children)) {
|
|
newChildren[grandChildOutlet] = grandChild;
|
|
}
|
|
} else if (childCandidate.segments.length > 0 || childCandidate.hasChildren()) {
|
|
newChildren[childOutlet] = childCandidate;
|
|
}
|
|
}
|
|
const s = new UrlSegmentGroup(segmentGroup.segments, newChildren);
|
|
return mergeTrivialChildren(s);
|
|
}
|
|
function mergeTrivialChildren(s) {
|
|
if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) {
|
|
const c = s.children[PRIMARY_OUTLET];
|
|
return new UrlSegmentGroup(s.segments.concat(c.segments), c.children);
|
|
}
|
|
return s;
|
|
}
|
|
function isUrlTree(v) {
|
|
return v instanceof UrlTree;
|
|
}
|
|
|
|
function createUrlTreeFromSnapshot(relativeTo, commands, queryParams = null, fragment = null, urlSerializer = new DefaultUrlSerializer()) {
|
|
const relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeTo);
|
|
return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment, urlSerializer);
|
|
}
|
|
function createSegmentGroupFromRoute(route) {
|
|
let targetGroup;
|
|
function createSegmentGroupFromRouteRecursive(currentRoute) {
|
|
const childOutlets = {};
|
|
for (const childSnapshot of currentRoute.children) {
|
|
const root = createSegmentGroupFromRouteRecursive(childSnapshot);
|
|
childOutlets[childSnapshot.outlet] = root;
|
|
}
|
|
const segmentGroup = new UrlSegmentGroup(currentRoute.url, childOutlets);
|
|
if (currentRoute === route) {
|
|
targetGroup = segmentGroup;
|
|
}
|
|
return segmentGroup;
|
|
}
|
|
const rootCandidate = createSegmentGroupFromRouteRecursive(route.root);
|
|
const rootSegmentGroup = createRoot(rootCandidate);
|
|
return targetGroup ?? rootSegmentGroup;
|
|
}
|
|
function createUrlTreeFromSegmentGroup(relativeTo, commands, queryParams, fragment, urlSerializer) {
|
|
let root = relativeTo;
|
|
while (root.parent) {
|
|
root = root.parent;
|
|
}
|
|
if (commands.length === 0) {
|
|
return tree(root, root, root, queryParams, fragment, urlSerializer);
|
|
}
|
|
const nav = computeNavigation(commands);
|
|
if (nav.toRoot()) {
|
|
return tree(root, root, new UrlSegmentGroup([], {}), queryParams, fragment, urlSerializer);
|
|
}
|
|
const position = findStartingPositionForTargetGroup(nav, root, relativeTo);
|
|
const newSegmentGroup = position.processChildren ? updateSegmentGroupChildren(position.segmentGroup, position.index, nav.commands) : updateSegmentGroup(position.segmentGroup, position.index, nav.commands);
|
|
return tree(root, position.segmentGroup, newSegmentGroup, queryParams, fragment, urlSerializer);
|
|
}
|
|
function isMatrixParams(command) {
|
|
return typeof command === 'object' && command != null && !command.outlets && !command.segmentPath;
|
|
}
|
|
function isCommandWithOutlets(command) {
|
|
return typeof command === 'object' && command != null && command.outlets;
|
|
}
|
|
function normalizeQueryParams(k, v, urlSerializer) {
|
|
k ||= 'ɵ';
|
|
const tree = new UrlTree();
|
|
tree.queryParams = {
|
|
[k]: v
|
|
};
|
|
return urlSerializer.parse(urlSerializer.serialize(tree)).queryParams[k];
|
|
}
|
|
function tree(oldRoot, oldSegmentGroup, newSegmentGroup, queryParams, fragment, urlSerializer) {
|
|
const qp = {};
|
|
for (const [key, value] of Object.entries(queryParams ?? {})) {
|
|
qp[key] = Array.isArray(value) ? value.map(v => normalizeQueryParams(key, v, urlSerializer)) : normalizeQueryParams(key, value, urlSerializer);
|
|
}
|
|
let rootCandidate;
|
|
if (oldRoot === oldSegmentGroup) {
|
|
rootCandidate = newSegmentGroup;
|
|
} else {
|
|
rootCandidate = replaceSegment(oldRoot, oldSegmentGroup, newSegmentGroup);
|
|
}
|
|
const newRoot = createRoot(squashSegmentGroup(rootCandidate));
|
|
return new UrlTree(newRoot, qp, fragment);
|
|
}
|
|
function replaceSegment(current, oldSegment, newSegment) {
|
|
const children = {};
|
|
Object.entries(current.children).forEach(([outletName, c]) => {
|
|
if (c === oldSegment) {
|
|
children[outletName] = newSegment;
|
|
} else {
|
|
children[outletName] = replaceSegment(c, oldSegment, newSegment);
|
|
}
|
|
});
|
|
return new UrlSegmentGroup(current.segments, children);
|
|
}
|
|
class Navigation {
|
|
isAbsolute;
|
|
numberOfDoubleDots;
|
|
commands;
|
|
constructor(isAbsolute, numberOfDoubleDots, commands) {
|
|
this.isAbsolute = isAbsolute;
|
|
this.numberOfDoubleDots = numberOfDoubleDots;
|
|
this.commands = commands;
|
|
if (isAbsolute && commands.length > 0 && isMatrixParams(commands[0])) {
|
|
throw new _RuntimeError(4003, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Root segment cannot have matrix parameters');
|
|
}
|
|
const cmdWithOutlet = commands.find(isCommandWithOutlets);
|
|
if (cmdWithOutlet && cmdWithOutlet !== last(commands)) {
|
|
throw new _RuntimeError(4004, (typeof ngDevMode === 'undefined' || ngDevMode) && '{outlets:{}} has to be the last command');
|
|
}
|
|
}
|
|
toRoot() {
|
|
return this.isAbsolute && this.commands.length === 1 && this.commands[0] == '/';
|
|
}
|
|
}
|
|
function computeNavigation(commands) {
|
|
if (typeof commands[0] === 'string' && commands.length === 1 && commands[0] === '/') {
|
|
return new Navigation(true, 0, commands);
|
|
}
|
|
let numberOfDoubleDots = 0;
|
|
let isAbsolute = false;
|
|
const res = commands.reduce((res, cmd, cmdIdx) => {
|
|
if (typeof cmd === 'object' && cmd != null) {
|
|
if (cmd.outlets) {
|
|
const outlets = {};
|
|
Object.entries(cmd.outlets).forEach(([name, commands]) => {
|
|
outlets[name] = typeof commands === 'string' ? commands.split('/') : commands;
|
|
});
|
|
return [...res, {
|
|
outlets
|
|
}];
|
|
}
|
|
if (cmd.segmentPath) {
|
|
return [...res, cmd.segmentPath];
|
|
}
|
|
}
|
|
if (!(typeof cmd === 'string')) {
|
|
return [...res, cmd];
|
|
}
|
|
if (cmdIdx === 0) {
|
|
cmd.split('/').forEach((urlPart, partIndex) => {
|
|
if (partIndex == 0 && urlPart === '.') ; else if (partIndex == 0 && urlPart === '') {
|
|
isAbsolute = true;
|
|
} else if (urlPart === '..') {
|
|
numberOfDoubleDots++;
|
|
} else if (urlPart != '') {
|
|
res.push(urlPart);
|
|
}
|
|
});
|
|
return res;
|
|
}
|
|
return [...res, cmd];
|
|
}, []);
|
|
return new Navigation(isAbsolute, numberOfDoubleDots, res);
|
|
}
|
|
class Position {
|
|
segmentGroup;
|
|
processChildren;
|
|
index;
|
|
constructor(segmentGroup, processChildren, index) {
|
|
this.segmentGroup = segmentGroup;
|
|
this.processChildren = processChildren;
|
|
this.index = index;
|
|
}
|
|
}
|
|
function findStartingPositionForTargetGroup(nav, root, target) {
|
|
if (nav.isAbsolute) {
|
|
return new Position(root, true, 0);
|
|
}
|
|
if (!target) {
|
|
return new Position(root, false, NaN);
|
|
}
|
|
if (target.parent === null) {
|
|
return new Position(target, true, 0);
|
|
}
|
|
const modifier = isMatrixParams(nav.commands[0]) ? 0 : 1;
|
|
const index = target.segments.length - 1 + modifier;
|
|
return createPositionApplyingDoubleDots(target, index, nav.numberOfDoubleDots);
|
|
}
|
|
function createPositionApplyingDoubleDots(group, index, numberOfDoubleDots) {
|
|
let g = group;
|
|
let ci = index;
|
|
let dd = numberOfDoubleDots;
|
|
while (dd > ci) {
|
|
dd -= ci;
|
|
g = g.parent;
|
|
if (!g) {
|
|
throw new _RuntimeError(4005, (typeof ngDevMode === 'undefined' || ngDevMode) && "Invalid number of '../'");
|
|
}
|
|
ci = g.segments.length;
|
|
}
|
|
return new Position(g, false, ci - dd);
|
|
}
|
|
function getOutlets(commands) {
|
|
if (isCommandWithOutlets(commands[0])) {
|
|
return commands[0].outlets;
|
|
}
|
|
return {
|
|
[PRIMARY_OUTLET]: commands
|
|
};
|
|
}
|
|
function updateSegmentGroup(segmentGroup, startIndex, commands) {
|
|
segmentGroup ??= new UrlSegmentGroup([], {});
|
|
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
|
return updateSegmentGroupChildren(segmentGroup, startIndex, commands);
|
|
}
|
|
const m = prefixedWith(segmentGroup, startIndex, commands);
|
|
const slicedCommands = commands.slice(m.commandIndex);
|
|
if (m.match && m.pathIndex < segmentGroup.segments.length) {
|
|
const g = new UrlSegmentGroup(segmentGroup.segments.slice(0, m.pathIndex), {});
|
|
g.children[PRIMARY_OUTLET] = new UrlSegmentGroup(segmentGroup.segments.slice(m.pathIndex), segmentGroup.children);
|
|
return updateSegmentGroupChildren(g, 0, slicedCommands);
|
|
} else if (m.match && slicedCommands.length === 0) {
|
|
return new UrlSegmentGroup(segmentGroup.segments, {});
|
|
} else if (m.match && !segmentGroup.hasChildren()) {
|
|
return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
|
} else if (m.match) {
|
|
return updateSegmentGroupChildren(segmentGroup, 0, slicedCommands);
|
|
} else {
|
|
return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
|
}
|
|
}
|
|
function updateSegmentGroupChildren(segmentGroup, startIndex, commands) {
|
|
if (commands.length === 0) {
|
|
return new UrlSegmentGroup(segmentGroup.segments, {});
|
|
} else {
|
|
const outlets = getOutlets(commands);
|
|
const children = {};
|
|
if (Object.keys(outlets).some(o => o !== PRIMARY_OUTLET) && segmentGroup.children[PRIMARY_OUTLET] && segmentGroup.numberOfChildren === 1 && segmentGroup.children[PRIMARY_OUTLET].segments.length === 0) {
|
|
const childrenOfEmptyChild = updateSegmentGroupChildren(segmentGroup.children[PRIMARY_OUTLET], startIndex, commands);
|
|
return new UrlSegmentGroup(segmentGroup.segments, childrenOfEmptyChild.children);
|
|
}
|
|
Object.entries(outlets).forEach(([outlet, commands]) => {
|
|
if (typeof commands === 'string') {
|
|
commands = [commands];
|
|
}
|
|
if (commands !== null) {
|
|
children[outlet] = updateSegmentGroup(segmentGroup.children[outlet], startIndex, commands);
|
|
}
|
|
});
|
|
Object.entries(segmentGroup.children).forEach(([childOutlet, child]) => {
|
|
if (outlets[childOutlet] === undefined) {
|
|
children[childOutlet] = child;
|
|
}
|
|
});
|
|
return new UrlSegmentGroup(segmentGroup.segments, children);
|
|
}
|
|
}
|
|
function prefixedWith(segmentGroup, startIndex, commands) {
|
|
let currentCommandIndex = 0;
|
|
let currentPathIndex = startIndex;
|
|
const noMatch = {
|
|
match: false,
|
|
pathIndex: 0,
|
|
commandIndex: 0
|
|
};
|
|
while (currentPathIndex < segmentGroup.segments.length) {
|
|
if (currentCommandIndex >= commands.length) return noMatch;
|
|
const path = segmentGroup.segments[currentPathIndex];
|
|
const command = commands[currentCommandIndex];
|
|
if (isCommandWithOutlets(command)) {
|
|
break;
|
|
}
|
|
const curr = `${command}`;
|
|
const next = currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
|
|
if (currentPathIndex > 0 && curr === undefined) break;
|
|
if (curr && next && typeof next === 'object' && next.outlets === undefined) {
|
|
if (!compare(curr, next, path)) return noMatch;
|
|
currentCommandIndex += 2;
|
|
} else {
|
|
if (!compare(curr, {}, path)) return noMatch;
|
|
currentCommandIndex++;
|
|
}
|
|
currentPathIndex++;
|
|
}
|
|
return {
|
|
match: true,
|
|
pathIndex: currentPathIndex,
|
|
commandIndex: currentCommandIndex
|
|
};
|
|
}
|
|
function createNewSegmentGroup(segmentGroup, startIndex, commands) {
|
|
const paths = segmentGroup.segments.slice(0, startIndex);
|
|
let i = 0;
|
|
while (i < commands.length) {
|
|
const command = commands[i];
|
|
if (isCommandWithOutlets(command)) {
|
|
const children = createNewSegmentChildren(command.outlets);
|
|
return new UrlSegmentGroup(paths, children);
|
|
}
|
|
if (i === 0 && isMatrixParams(commands[0])) {
|
|
const p = segmentGroup.segments[startIndex];
|
|
paths.push(new UrlSegment(p.path, stringify(commands[0])));
|
|
i++;
|
|
continue;
|
|
}
|
|
const curr = isCommandWithOutlets(command) ? command.outlets[PRIMARY_OUTLET] : `${command}`;
|
|
const next = i < commands.length - 1 ? commands[i + 1] : null;
|
|
if (curr && next && isMatrixParams(next)) {
|
|
paths.push(new UrlSegment(curr, stringify(next)));
|
|
i += 2;
|
|
} else {
|
|
paths.push(new UrlSegment(curr, {}));
|
|
i++;
|
|
}
|
|
}
|
|
return new UrlSegmentGroup(paths, {});
|
|
}
|
|
function createNewSegmentChildren(outlets) {
|
|
const children = {};
|
|
Object.entries(outlets).forEach(([outlet, commands]) => {
|
|
if (typeof commands === 'string') {
|
|
commands = [commands];
|
|
}
|
|
if (commands !== null) {
|
|
children[outlet] = createNewSegmentGroup(new UrlSegmentGroup([], {}), 0, commands);
|
|
}
|
|
});
|
|
return children;
|
|
}
|
|
function stringify(params) {
|
|
const res = {};
|
|
Object.entries(params).forEach(([k, v]) => res[k] = `${v}`);
|
|
return res;
|
|
}
|
|
function compare(path, params, segment) {
|
|
return path == segment.path && shallowEqual(params, segment.parameters);
|
|
}
|
|
|
|
const IMPERATIVE_NAVIGATION = 'imperative';
|
|
var EventType;
|
|
(function (EventType) {
|
|
EventType[EventType["NavigationStart"] = 0] = "NavigationStart";
|
|
EventType[EventType["NavigationEnd"] = 1] = "NavigationEnd";
|
|
EventType[EventType["NavigationCancel"] = 2] = "NavigationCancel";
|
|
EventType[EventType["NavigationError"] = 3] = "NavigationError";
|
|
EventType[EventType["RoutesRecognized"] = 4] = "RoutesRecognized";
|
|
EventType[EventType["ResolveStart"] = 5] = "ResolveStart";
|
|
EventType[EventType["ResolveEnd"] = 6] = "ResolveEnd";
|
|
EventType[EventType["GuardsCheckStart"] = 7] = "GuardsCheckStart";
|
|
EventType[EventType["GuardsCheckEnd"] = 8] = "GuardsCheckEnd";
|
|
EventType[EventType["RouteConfigLoadStart"] = 9] = "RouteConfigLoadStart";
|
|
EventType[EventType["RouteConfigLoadEnd"] = 10] = "RouteConfigLoadEnd";
|
|
EventType[EventType["ChildActivationStart"] = 11] = "ChildActivationStart";
|
|
EventType[EventType["ChildActivationEnd"] = 12] = "ChildActivationEnd";
|
|
EventType[EventType["ActivationStart"] = 13] = "ActivationStart";
|
|
EventType[EventType["ActivationEnd"] = 14] = "ActivationEnd";
|
|
EventType[EventType["Scroll"] = 15] = "Scroll";
|
|
EventType[EventType["NavigationSkipped"] = 16] = "NavigationSkipped";
|
|
})(EventType || (EventType = {}));
|
|
class RouterEvent {
|
|
id;
|
|
url;
|
|
constructor(id, url) {
|
|
this.id = id;
|
|
this.url = url;
|
|
}
|
|
}
|
|
class NavigationStart extends RouterEvent {
|
|
type = EventType.NavigationStart;
|
|
navigationTrigger;
|
|
restoredState;
|
|
constructor(id, url, navigationTrigger = 'imperative', restoredState = null) {
|
|
super(id, url);
|
|
this.navigationTrigger = navigationTrigger;
|
|
this.restoredState = restoredState;
|
|
}
|
|
toString() {
|
|
return `NavigationStart(id: ${this.id}, url: '${this.url}')`;
|
|
}
|
|
}
|
|
class NavigationEnd extends RouterEvent {
|
|
urlAfterRedirects;
|
|
type = EventType.NavigationEnd;
|
|
constructor(id, url, urlAfterRedirects) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
}
|
|
toString() {
|
|
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
|
|
}
|
|
}
|
|
var NavigationCancellationCode;
|
|
(function (NavigationCancellationCode) {
|
|
NavigationCancellationCode[NavigationCancellationCode["Redirect"] = 0] = "Redirect";
|
|
NavigationCancellationCode[NavigationCancellationCode["SupersededByNewNavigation"] = 1] = "SupersededByNewNavigation";
|
|
NavigationCancellationCode[NavigationCancellationCode["NoDataFromResolver"] = 2] = "NoDataFromResolver";
|
|
NavigationCancellationCode[NavigationCancellationCode["GuardRejected"] = 3] = "GuardRejected";
|
|
NavigationCancellationCode[NavigationCancellationCode["Aborted"] = 4] = "Aborted";
|
|
})(NavigationCancellationCode || (NavigationCancellationCode = {}));
|
|
var NavigationSkippedCode;
|
|
(function (NavigationSkippedCode) {
|
|
NavigationSkippedCode[NavigationSkippedCode["IgnoredSameUrlNavigation"] = 0] = "IgnoredSameUrlNavigation";
|
|
NavigationSkippedCode[NavigationSkippedCode["IgnoredByUrlHandlingStrategy"] = 1] = "IgnoredByUrlHandlingStrategy";
|
|
})(NavigationSkippedCode || (NavigationSkippedCode = {}));
|
|
class NavigationCancel extends RouterEvent {
|
|
reason;
|
|
code;
|
|
type = EventType.NavigationCancel;
|
|
constructor(id, url, reason, code) {
|
|
super(id, url);
|
|
this.reason = reason;
|
|
this.code = code;
|
|
}
|
|
toString() {
|
|
return `NavigationCancel(id: ${this.id}, url: '${this.url}')`;
|
|
}
|
|
}
|
|
function isRedirectingEvent(event) {
|
|
return event instanceof NavigationCancel && (event.code === NavigationCancellationCode.Redirect || event.code === NavigationCancellationCode.SupersededByNewNavigation);
|
|
}
|
|
class NavigationSkipped extends RouterEvent {
|
|
reason;
|
|
code;
|
|
type = EventType.NavigationSkipped;
|
|
constructor(id, url, reason, code) {
|
|
super(id, url);
|
|
this.reason = reason;
|
|
this.code = code;
|
|
}
|
|
}
|
|
class NavigationError extends RouterEvent {
|
|
error;
|
|
target;
|
|
type = EventType.NavigationError;
|
|
constructor(id, url, error, target) {
|
|
super(id, url);
|
|
this.error = error;
|
|
this.target = target;
|
|
}
|
|
toString() {
|
|
return `NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`;
|
|
}
|
|
}
|
|
class RoutesRecognized extends RouterEvent {
|
|
urlAfterRedirects;
|
|
state;
|
|
type = EventType.RoutesRecognized;
|
|
constructor(id, url, urlAfterRedirects, state) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
this.state = state;
|
|
}
|
|
toString() {
|
|
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
|
}
|
|
}
|
|
class GuardsCheckStart extends RouterEvent {
|
|
urlAfterRedirects;
|
|
state;
|
|
type = EventType.GuardsCheckStart;
|
|
constructor(id, url, urlAfterRedirects, state) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
this.state = state;
|
|
}
|
|
toString() {
|
|
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
|
}
|
|
}
|
|
class GuardsCheckEnd extends RouterEvent {
|
|
urlAfterRedirects;
|
|
state;
|
|
shouldActivate;
|
|
type = EventType.GuardsCheckEnd;
|
|
constructor(id, url, urlAfterRedirects, state, shouldActivate) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
this.state = state;
|
|
this.shouldActivate = shouldActivate;
|
|
}
|
|
toString() {
|
|
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
|
}
|
|
}
|
|
class ResolveStart extends RouterEvent {
|
|
urlAfterRedirects;
|
|
state;
|
|
type = EventType.ResolveStart;
|
|
constructor(id, url, urlAfterRedirects, state) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
this.state = state;
|
|
}
|
|
toString() {
|
|
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
|
}
|
|
}
|
|
class ResolveEnd extends RouterEvent {
|
|
urlAfterRedirects;
|
|
state;
|
|
type = EventType.ResolveEnd;
|
|
constructor(id, url, urlAfterRedirects, state) {
|
|
super(id, url);
|
|
this.urlAfterRedirects = urlAfterRedirects;
|
|
this.state = state;
|
|
}
|
|
toString() {
|
|
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
|
}
|
|
}
|
|
class RouteConfigLoadStart {
|
|
route;
|
|
type = EventType.RouteConfigLoadStart;
|
|
constructor(route) {
|
|
this.route = route;
|
|
}
|
|
toString() {
|
|
return `RouteConfigLoadStart(path: ${this.route.path})`;
|
|
}
|
|
}
|
|
class RouteConfigLoadEnd {
|
|
route;
|
|
type = EventType.RouteConfigLoadEnd;
|
|
constructor(route) {
|
|
this.route = route;
|
|
}
|
|
toString() {
|
|
return `RouteConfigLoadEnd(path: ${this.route.path})`;
|
|
}
|
|
}
|
|
class ChildActivationStart {
|
|
snapshot;
|
|
type = EventType.ChildActivationStart;
|
|
constructor(snapshot) {
|
|
this.snapshot = snapshot;
|
|
}
|
|
toString() {
|
|
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
|
return `ChildActivationStart(path: '${path}')`;
|
|
}
|
|
}
|
|
class ChildActivationEnd {
|
|
snapshot;
|
|
type = EventType.ChildActivationEnd;
|
|
constructor(snapshot) {
|
|
this.snapshot = snapshot;
|
|
}
|
|
toString() {
|
|
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
|
return `ChildActivationEnd(path: '${path}')`;
|
|
}
|
|
}
|
|
class ActivationStart {
|
|
snapshot;
|
|
type = EventType.ActivationStart;
|
|
constructor(snapshot) {
|
|
this.snapshot = snapshot;
|
|
}
|
|
toString() {
|
|
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
|
return `ActivationStart(path: '${path}')`;
|
|
}
|
|
}
|
|
class ActivationEnd {
|
|
snapshot;
|
|
type = EventType.ActivationEnd;
|
|
constructor(snapshot) {
|
|
this.snapshot = snapshot;
|
|
}
|
|
toString() {
|
|
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
|
return `ActivationEnd(path: '${path}')`;
|
|
}
|
|
}
|
|
class Scroll {
|
|
routerEvent;
|
|
position;
|
|
anchor;
|
|
scrollBehavior;
|
|
type = EventType.Scroll;
|
|
constructor(routerEvent, position, anchor, scrollBehavior) {
|
|
this.routerEvent = routerEvent;
|
|
this.position = position;
|
|
this.anchor = anchor;
|
|
this.scrollBehavior = scrollBehavior;
|
|
}
|
|
toString() {
|
|
const pos = this.position ? `${this.position[0]}, ${this.position[1]}` : null;
|
|
return `Scroll(anchor: '${this.anchor}', position: '${pos}')`;
|
|
}
|
|
}
|
|
class BeforeActivateRoutes {}
|
|
class RedirectRequest {
|
|
url;
|
|
navigationBehaviorOptions;
|
|
constructor(url, navigationBehaviorOptions) {
|
|
this.url = url;
|
|
this.navigationBehaviorOptions = navigationBehaviorOptions;
|
|
}
|
|
}
|
|
function isPublicRouterEvent(e) {
|
|
return !(e instanceof BeforeActivateRoutes) && !(e instanceof RedirectRequest);
|
|
}
|
|
function stringifyEvent(routerEvent) {
|
|
switch (routerEvent.type) {
|
|
case EventType.ActivationEnd:
|
|
return `ActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
|
case EventType.ActivationStart:
|
|
return `ActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
|
case EventType.ChildActivationEnd:
|
|
return `ChildActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
|
case EventType.ChildActivationStart:
|
|
return `ChildActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
|
case EventType.GuardsCheckEnd:
|
|
return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
|
|
case EventType.GuardsCheckStart:
|
|
return `GuardsCheckStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
|
case EventType.NavigationCancel:
|
|
return `NavigationCancel(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
|
case EventType.NavigationSkipped:
|
|
return `NavigationSkipped(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
|
case EventType.NavigationEnd:
|
|
return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}')`;
|
|
case EventType.NavigationError:
|
|
return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${routerEvent.error})`;
|
|
case EventType.NavigationStart:
|
|
return `NavigationStart(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
|
case EventType.ResolveEnd:
|
|
return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
|
case EventType.ResolveStart:
|
|
return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
|
case EventType.RouteConfigLoadEnd:
|
|
return `RouteConfigLoadEnd(path: ${routerEvent.route.path})`;
|
|
case EventType.RouteConfigLoadStart:
|
|
return `RouteConfigLoadStart(path: ${routerEvent.route.path})`;
|
|
case EventType.RoutesRecognized:
|
|
return `RoutesRecognized(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
|
case EventType.Scroll:
|
|
const pos = routerEvent.position ? `${routerEvent.position[0]}, ${routerEvent.position[1]}` : null;
|
|
return `Scroll(anchor: '${routerEvent.anchor}', position: '${pos}')`;
|
|
}
|
|
}
|
|
|
|
class OutletContext {
|
|
rootInjector;
|
|
outlet = null;
|
|
route = null;
|
|
children;
|
|
attachRef = null;
|
|
get injector() {
|
|
return this.route?.snapshot._environmentInjector ?? this.rootInjector;
|
|
}
|
|
constructor(rootInjector) {
|
|
this.rootInjector = rootInjector;
|
|
this.children = new ChildrenOutletContexts(this.rootInjector);
|
|
}
|
|
}
|
|
class ChildrenOutletContexts {
|
|
rootInjector;
|
|
contexts = new Map();
|
|
constructor(rootInjector) {
|
|
this.rootInjector = rootInjector;
|
|
}
|
|
onChildOutletCreated(childName, outlet) {
|
|
const context = this.getOrCreateContext(childName);
|
|
context.outlet = outlet;
|
|
this.contexts.set(childName, context);
|
|
}
|
|
onChildOutletDestroyed(childName) {
|
|
const context = this.getContext(childName);
|
|
if (context) {
|
|
context.outlet = null;
|
|
context.attachRef = null;
|
|
}
|
|
}
|
|
onOutletDeactivated() {
|
|
const contexts = this.contexts;
|
|
this.contexts = new Map();
|
|
return contexts;
|
|
}
|
|
onOutletReAttached(contexts) {
|
|
this.contexts = contexts;
|
|
}
|
|
getOrCreateContext(childName) {
|
|
let context = this.getContext(childName);
|
|
if (!context) {
|
|
context = new OutletContext(this.rootInjector);
|
|
this.contexts.set(childName, context);
|
|
}
|
|
return context;
|
|
}
|
|
getContext(childName) {
|
|
return this.contexts.get(childName) || null;
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: ChildrenOutletContexts,
|
|
deps: [{
|
|
token: i0.EnvironmentInjector
|
|
}],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: ChildrenOutletContexts,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: ChildrenOutletContexts,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}],
|
|
ctorParameters: () => [{
|
|
type: i0.EnvironmentInjector
|
|
}]
|
|
});
|
|
|
|
class Tree {
|
|
_root;
|
|
constructor(root) {
|
|
this._root = root;
|
|
}
|
|
get root() {
|
|
return this._root.value;
|
|
}
|
|
parent(t) {
|
|
const p = this.pathFromRoot(t);
|
|
return p.length > 1 ? p[p.length - 2] : null;
|
|
}
|
|
children(t) {
|
|
const n = findNode(t, this._root);
|
|
return n ? n.children.map(t => t.value) : [];
|
|
}
|
|
firstChild(t) {
|
|
const n = findNode(t, this._root);
|
|
return n && n.children.length > 0 ? n.children[0].value : null;
|
|
}
|
|
siblings(t) {
|
|
const p = findPath(t, this._root);
|
|
if (p.length < 2) return [];
|
|
const c = p[p.length - 2].children.map(c => c.value);
|
|
return c.filter(cc => cc !== t);
|
|
}
|
|
pathFromRoot(t) {
|
|
return findPath(t, this._root).map(s => s.value);
|
|
}
|
|
}
|
|
function findNode(value, node) {
|
|
if (value === node.value) return node;
|
|
for (const child of node.children) {
|
|
const node = findNode(value, child);
|
|
if (node) return node;
|
|
}
|
|
return null;
|
|
}
|
|
function findPath(value, node) {
|
|
if (value === node.value) return [node];
|
|
for (const child of node.children) {
|
|
const path = findPath(value, child);
|
|
if (path.length) {
|
|
path.unshift(node);
|
|
return path;
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
class TreeNode {
|
|
value;
|
|
children;
|
|
constructor(value, children) {
|
|
this.value = value;
|
|
this.children = children;
|
|
}
|
|
toString() {
|
|
return `TreeNode(${this.value})`;
|
|
}
|
|
}
|
|
function nodeChildrenAsMap(node) {
|
|
const map = {};
|
|
if (node) {
|
|
node.children.forEach(child => map[child.value.outlet] = child);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
class RouterState extends Tree {
|
|
snapshot;
|
|
constructor(root, snapshot) {
|
|
super(root);
|
|
this.snapshot = snapshot;
|
|
setRouterState(this, root);
|
|
}
|
|
toString() {
|
|
return this.snapshot.toString();
|
|
}
|
|
}
|
|
function createEmptyState(rootComponent, injector) {
|
|
const snapshot = createEmptyStateSnapshot(rootComponent, injector);
|
|
const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]);
|
|
const emptyParams = new BehaviorSubject({});
|
|
const emptyData = new BehaviorSubject({});
|
|
const emptyQueryParams = new BehaviorSubject({});
|
|
const fragment = new BehaviorSubject('');
|
|
const activated = new ActivatedRoute(emptyUrl, emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, snapshot.root);
|
|
activated.snapshot = snapshot.root;
|
|
return new RouterState(new TreeNode(activated, []), snapshot);
|
|
}
|
|
function createEmptyStateSnapshot(rootComponent, injector) {
|
|
const emptyParams = {};
|
|
const emptyData = {};
|
|
const emptyQueryParams = {};
|
|
const fragment = '';
|
|
const activated = new ActivatedRouteSnapshot([], emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, null, {}, injector);
|
|
return new RouterStateSnapshot('', new TreeNode(activated, []));
|
|
}
|
|
class ActivatedRoute {
|
|
urlSubject;
|
|
paramsSubject;
|
|
queryParamsSubject;
|
|
fragmentSubject;
|
|
dataSubject;
|
|
outlet;
|
|
component;
|
|
snapshot;
|
|
_futureSnapshot;
|
|
_routerState;
|
|
_paramMap;
|
|
_queryParamMap;
|
|
title;
|
|
url;
|
|
params;
|
|
queryParams;
|
|
fragment;
|
|
data;
|
|
constructor(urlSubject, paramsSubject, queryParamsSubject, fragmentSubject, dataSubject, outlet, component, futureSnapshot) {
|
|
this.urlSubject = urlSubject;
|
|
this.paramsSubject = paramsSubject;
|
|
this.queryParamsSubject = queryParamsSubject;
|
|
this.fragmentSubject = fragmentSubject;
|
|
this.dataSubject = dataSubject;
|
|
this.outlet = outlet;
|
|
this.component = component;
|
|
this._futureSnapshot = futureSnapshot;
|
|
this.title = this.dataSubject?.pipe(map(d => d[RouteTitleKey])) ?? of(undefined);
|
|
this.url = urlSubject;
|
|
this.params = paramsSubject;
|
|
this.queryParams = queryParamsSubject;
|
|
this.fragment = fragmentSubject;
|
|
this.data = dataSubject;
|
|
}
|
|
get routeConfig() {
|
|
return this._futureSnapshot.routeConfig;
|
|
}
|
|
get root() {
|
|
return this._routerState.root;
|
|
}
|
|
get parent() {
|
|
return this._routerState.parent(this);
|
|
}
|
|
get firstChild() {
|
|
return this._routerState.firstChild(this);
|
|
}
|
|
get children() {
|
|
return this._routerState.children(this);
|
|
}
|
|
get pathFromRoot() {
|
|
return this._routerState.pathFromRoot(this);
|
|
}
|
|
get paramMap() {
|
|
this._paramMap ??= this.params.pipe(map(p => convertToParamMap(p)));
|
|
return this._paramMap;
|
|
}
|
|
get queryParamMap() {
|
|
this._queryParamMap ??= this.queryParams.pipe(map(p => convertToParamMap(p)));
|
|
return this._queryParamMap;
|
|
}
|
|
toString() {
|
|
return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`;
|
|
}
|
|
}
|
|
function getInherited(route, parent, paramsInheritanceStrategy = 'emptyOnly') {
|
|
let inherited;
|
|
const {
|
|
routeConfig
|
|
} = route;
|
|
if (parent !== null && (paramsInheritanceStrategy === 'always' || routeConfig?.path === '' || !parent.component && !parent.routeConfig?.loadComponent)) {
|
|
inherited = {
|
|
params: {
|
|
...parent.params,
|
|
...route.params
|
|
},
|
|
data: {
|
|
...parent.data,
|
|
...route.data
|
|
},
|
|
resolve: {
|
|
...route.data,
|
|
...parent.data,
|
|
...routeConfig?.data,
|
|
...route._resolvedData
|
|
}
|
|
};
|
|
} else {
|
|
inherited = {
|
|
params: {
|
|
...route.params
|
|
},
|
|
data: {
|
|
...route.data
|
|
},
|
|
resolve: {
|
|
...route.data,
|
|
...(route._resolvedData ?? {})
|
|
}
|
|
};
|
|
}
|
|
if (routeConfig && hasStaticTitle(routeConfig)) {
|
|
inherited.resolve[RouteTitleKey] = routeConfig.title;
|
|
}
|
|
return inherited;
|
|
}
|
|
class ActivatedRouteSnapshot {
|
|
url;
|
|
params;
|
|
queryParams;
|
|
fragment;
|
|
data;
|
|
outlet;
|
|
component;
|
|
routeConfig;
|
|
_resolve;
|
|
_resolvedData;
|
|
_routerState;
|
|
_paramMap;
|
|
_queryParamMap;
|
|
_environmentInjector;
|
|
get title() {
|
|
return this.data?.[RouteTitleKey];
|
|
}
|
|
constructor(url, params, queryParams, fragment, data, outlet, component, routeConfig, resolve, environmentInjector) {
|
|
this.url = url;
|
|
this.params = params;
|
|
this.queryParams = queryParams;
|
|
this.fragment = fragment;
|
|
this.data = data;
|
|
this.outlet = outlet;
|
|
this.component = component;
|
|
this.routeConfig = routeConfig;
|
|
this._resolve = resolve;
|
|
this._environmentInjector = environmentInjector;
|
|
}
|
|
get root() {
|
|
return this._routerState.root;
|
|
}
|
|
get parent() {
|
|
return this._routerState.parent(this);
|
|
}
|
|
get firstChild() {
|
|
return this._routerState.firstChild(this);
|
|
}
|
|
get children() {
|
|
return this._routerState.children(this);
|
|
}
|
|
get pathFromRoot() {
|
|
return this._routerState.pathFromRoot(this);
|
|
}
|
|
get paramMap() {
|
|
this._paramMap ??= convertToParamMap(this.params);
|
|
return this._paramMap;
|
|
}
|
|
get queryParamMap() {
|
|
this._queryParamMap ??= convertToParamMap(this.queryParams);
|
|
return this._queryParamMap;
|
|
}
|
|
toString() {
|
|
const url = this.url.map(segment => segment.toString()).join('/');
|
|
const matched = this.routeConfig ? this.routeConfig.path : '';
|
|
return `Route(url:'${url}', path:'${matched}')`;
|
|
}
|
|
}
|
|
class RouterStateSnapshot extends Tree {
|
|
url;
|
|
constructor(url, root) {
|
|
super(root);
|
|
this.url = url;
|
|
setRouterState(this, root);
|
|
}
|
|
toString() {
|
|
return serializeNode(this._root);
|
|
}
|
|
}
|
|
function setRouterState(state, node) {
|
|
node.value._routerState = state;
|
|
node.children.forEach(c => setRouterState(state, c));
|
|
}
|
|
function serializeNode(node) {
|
|
const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : '';
|
|
return `${node.value}${c}`;
|
|
}
|
|
function advanceActivatedRoute(route) {
|
|
if (route.snapshot) {
|
|
const currentSnapshot = route.snapshot;
|
|
const nextSnapshot = route._futureSnapshot;
|
|
route.snapshot = nextSnapshot;
|
|
if (!shallowEqual(currentSnapshot.queryParams, nextSnapshot.queryParams)) {
|
|
route.queryParamsSubject.next(nextSnapshot.queryParams);
|
|
}
|
|
if (currentSnapshot.fragment !== nextSnapshot.fragment) {
|
|
route.fragmentSubject.next(nextSnapshot.fragment);
|
|
}
|
|
if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) {
|
|
route.paramsSubject.next(nextSnapshot.params);
|
|
}
|
|
if (!shallowEqualArrays(currentSnapshot.url, nextSnapshot.url)) {
|
|
route.urlSubject.next(nextSnapshot.url);
|
|
}
|
|
if (!shallowEqual(currentSnapshot.data, nextSnapshot.data)) {
|
|
route.dataSubject.next(nextSnapshot.data);
|
|
}
|
|
} else {
|
|
route.snapshot = route._futureSnapshot;
|
|
route.dataSubject.next(route._futureSnapshot.data);
|
|
}
|
|
}
|
|
function equalParamsAndUrlSegments(a, b) {
|
|
const equalUrlParams = shallowEqual(a.params, b.params) && equalSegments(a.url, b.url);
|
|
const parentsMismatch = !a.parent !== !b.parent;
|
|
return equalUrlParams && !parentsMismatch && (!a.parent || equalParamsAndUrlSegments(a.parent, b.parent));
|
|
}
|
|
function hasStaticTitle(config) {
|
|
return typeof config.title === 'string' || config.title === null;
|
|
}
|
|
|
|
const ROUTER_OUTLET_DATA = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'RouterOutlet data' : '');
|
|
class RouterOutlet {
|
|
activated = null;
|
|
get activatedComponentRef() {
|
|
return this.activated;
|
|
}
|
|
_activatedRoute = null;
|
|
name = PRIMARY_OUTLET;
|
|
activateEvents = new EventEmitter();
|
|
deactivateEvents = new EventEmitter();
|
|
attachEvents = new EventEmitter();
|
|
detachEvents = new EventEmitter();
|
|
routerOutletData = input(...(ngDevMode ? [undefined, {
|
|
debugName: "routerOutletData"
|
|
}] : []));
|
|
parentContexts = inject(ChildrenOutletContexts);
|
|
location = inject(ViewContainerRef);
|
|
changeDetector = inject(ChangeDetectorRef);
|
|
inputBinder = inject(INPUT_BINDER, {
|
|
optional: true
|
|
});
|
|
supportsBindingToComponentInputs = true;
|
|
ngOnChanges(changes) {
|
|
if (changes['name']) {
|
|
const {
|
|
firstChange,
|
|
previousValue
|
|
} = changes['name'];
|
|
if (firstChange) {
|
|
return;
|
|
}
|
|
if (this.isTrackedInParentContexts(previousValue)) {
|
|
this.deactivate();
|
|
this.parentContexts.onChildOutletDestroyed(previousValue);
|
|
}
|
|
this.initializeOutletWithName();
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
if (this.isTrackedInParentContexts(this.name)) {
|
|
this.parentContexts.onChildOutletDestroyed(this.name);
|
|
}
|
|
this.inputBinder?.unsubscribeFromRouteData(this);
|
|
}
|
|
isTrackedInParentContexts(outletName) {
|
|
return this.parentContexts.getContext(outletName)?.outlet === this;
|
|
}
|
|
ngOnInit() {
|
|
this.initializeOutletWithName();
|
|
}
|
|
initializeOutletWithName() {
|
|
this.parentContexts.onChildOutletCreated(this.name, this);
|
|
if (this.activated) {
|
|
return;
|
|
}
|
|
const context = this.parentContexts.getContext(this.name);
|
|
if (context?.route) {
|
|
if (context.attachRef) {
|
|
this.attach(context.attachRef, context.route);
|
|
} else {
|
|
this.activateWith(context.route, context.injector);
|
|
}
|
|
}
|
|
}
|
|
get isActivated() {
|
|
return !!this.activated;
|
|
}
|
|
get component() {
|
|
if (!this.activated) throw new _RuntimeError(4012, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
|
return this.activated.instance;
|
|
}
|
|
get activatedRoute() {
|
|
if (!this.activated) throw new _RuntimeError(4012, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
|
return this._activatedRoute;
|
|
}
|
|
get activatedRouteData() {
|
|
if (this._activatedRoute) {
|
|
return this._activatedRoute.snapshot.data;
|
|
}
|
|
return {};
|
|
}
|
|
detach() {
|
|
if (!this.activated) throw new _RuntimeError(4012, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
|
this.location.detach();
|
|
const cmp = this.activated;
|
|
this.activated = null;
|
|
this._activatedRoute = null;
|
|
this.detachEvents.emit(cmp.instance);
|
|
return cmp;
|
|
}
|
|
attach(ref, activatedRoute) {
|
|
this.activated = ref;
|
|
this._activatedRoute = activatedRoute;
|
|
this.location.insert(ref.hostView);
|
|
this.inputBinder?.bindActivatedRouteToOutletComponent(this);
|
|
this.attachEvents.emit(ref.instance);
|
|
}
|
|
deactivate() {
|
|
if (this.activated) {
|
|
const c = this.component;
|
|
this.activated.destroy();
|
|
this.activated = null;
|
|
this._activatedRoute = null;
|
|
this.deactivateEvents.emit(c);
|
|
}
|
|
}
|
|
activateWith(activatedRoute, environmentInjector) {
|
|
if (this.isActivated) {
|
|
throw new _RuntimeError(4013, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Cannot activate an already activated outlet');
|
|
}
|
|
this._activatedRoute = activatedRoute;
|
|
const location = this.location;
|
|
const snapshot = activatedRoute.snapshot;
|
|
const component = snapshot.component;
|
|
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
|
const injector = new OutletInjector(activatedRoute, childContexts, location.injector, this.routerOutletData);
|
|
this.activated = location.createComponent(component, {
|
|
index: location.length,
|
|
injector,
|
|
environmentInjector: environmentInjector
|
|
});
|
|
this.changeDetector.markForCheck();
|
|
this.inputBinder?.bindActivatedRouteToOutletComponent(this);
|
|
this.activateEvents.emit(this.activated.instance);
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouterOutlet,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Directive
|
|
});
|
|
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
minVersion: "17.1.0",
|
|
version: "21.1.1",
|
|
type: RouterOutlet,
|
|
isStandalone: true,
|
|
selector: "router-outlet",
|
|
inputs: {
|
|
name: {
|
|
classPropertyName: "name",
|
|
publicName: "name",
|
|
isSignal: false,
|
|
isRequired: false,
|
|
transformFunction: null
|
|
},
|
|
routerOutletData: {
|
|
classPropertyName: "routerOutletData",
|
|
publicName: "routerOutletData",
|
|
isSignal: true,
|
|
isRequired: false,
|
|
transformFunction: null
|
|
}
|
|
},
|
|
outputs: {
|
|
activateEvents: "activate",
|
|
deactivateEvents: "deactivate",
|
|
attachEvents: "attach",
|
|
detachEvents: "detach"
|
|
},
|
|
exportAs: ["outlet"],
|
|
usesOnChanges: true,
|
|
ngImport: i0
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouterOutlet,
|
|
decorators: [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: 'router-outlet',
|
|
exportAs: 'outlet'
|
|
}]
|
|
}],
|
|
propDecorators: {
|
|
name: [{
|
|
type: Input
|
|
}],
|
|
activateEvents: [{
|
|
type: Output,
|
|
args: ['activate']
|
|
}],
|
|
deactivateEvents: [{
|
|
type: Output,
|
|
args: ['deactivate']
|
|
}],
|
|
attachEvents: [{
|
|
type: Output,
|
|
args: ['attach']
|
|
}],
|
|
detachEvents: [{
|
|
type: Output,
|
|
args: ['detach']
|
|
}],
|
|
routerOutletData: [{
|
|
type: i0.Input,
|
|
args: [{
|
|
isSignal: true,
|
|
alias: "routerOutletData",
|
|
required: false
|
|
}]
|
|
}]
|
|
}
|
|
});
|
|
class OutletInjector {
|
|
route;
|
|
childContexts;
|
|
parent;
|
|
outletData;
|
|
constructor(route, childContexts, parent, outletData) {
|
|
this.route = route;
|
|
this.childContexts = childContexts;
|
|
this.parent = parent;
|
|
this.outletData = outletData;
|
|
}
|
|
get(token, notFoundValue) {
|
|
if (token === ActivatedRoute) {
|
|
return this.route;
|
|
}
|
|
if (token === ChildrenOutletContexts) {
|
|
return this.childContexts;
|
|
}
|
|
if (token === ROUTER_OUTLET_DATA) {
|
|
return this.outletData;
|
|
}
|
|
return this.parent.get(token, notFoundValue);
|
|
}
|
|
}
|
|
const INPUT_BINDER = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'Router Input Binder' : '');
|
|
class RoutedComponentInputBinder {
|
|
outletDataSubscriptions = new Map();
|
|
bindActivatedRouteToOutletComponent(outlet) {
|
|
this.unsubscribeFromRouteData(outlet);
|
|
this.subscribeToRouteData(outlet);
|
|
}
|
|
unsubscribeFromRouteData(outlet) {
|
|
this.outletDataSubscriptions.get(outlet)?.unsubscribe();
|
|
this.outletDataSubscriptions.delete(outlet);
|
|
}
|
|
subscribeToRouteData(outlet) {
|
|
const {
|
|
activatedRoute
|
|
} = outlet;
|
|
const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data]).pipe(switchMap(([queryParams, params, data], index) => {
|
|
data = {
|
|
...queryParams,
|
|
...params,
|
|
...data
|
|
};
|
|
if (index === 0) {
|
|
return of(data);
|
|
}
|
|
return Promise.resolve(data);
|
|
})).subscribe(data => {
|
|
if (!outlet.isActivated || !outlet.activatedComponentRef || outlet.activatedRoute !== activatedRoute || activatedRoute.component === null) {
|
|
this.unsubscribeFromRouteData(outlet);
|
|
return;
|
|
}
|
|
const mirror = reflectComponentType(activatedRoute.component);
|
|
if (!mirror) {
|
|
this.unsubscribeFromRouteData(outlet);
|
|
return;
|
|
}
|
|
for (const {
|
|
templateName
|
|
} of mirror.inputs) {
|
|
outlet.activatedComponentRef.setInput(templateName, data[templateName]);
|
|
}
|
|
});
|
|
this.outletDataSubscriptions.set(outlet, dataSubscription);
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RoutedComponentInputBinder,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RoutedComponentInputBinder
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RoutedComponentInputBinder,
|
|
decorators: [{
|
|
type: Injectable
|
|
}]
|
|
});
|
|
|
|
class ɵEmptyOutletComponent {
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: ɵEmptyOutletComponent,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Component
|
|
});
|
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
minVersion: "14.0.0",
|
|
version: "21.1.1",
|
|
type: ɵEmptyOutletComponent,
|
|
isStandalone: true,
|
|
selector: "ng-component",
|
|
exportAs: ["emptyRouterOutlet"],
|
|
ngImport: i0,
|
|
template: `<router-outlet />`,
|
|
isInline: true,
|
|
dependencies: [{
|
|
kind: "directive",
|
|
type: RouterOutlet,
|
|
selector: "router-outlet",
|
|
inputs: ["name", "routerOutletData"],
|
|
outputs: ["activate", "deactivate", "attach", "detach"],
|
|
exportAs: ["outlet"]
|
|
}]
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: ɵEmptyOutletComponent,
|
|
decorators: [{
|
|
type: Component,
|
|
args: [{
|
|
template: `<router-outlet />`,
|
|
imports: [RouterOutlet],
|
|
exportAs: 'emptyRouterOutlet'
|
|
}]
|
|
}]
|
|
});
|
|
function standardizeConfig(r) {
|
|
const children = r.children && r.children.map(standardizeConfig);
|
|
const c = children ? {
|
|
...r,
|
|
children
|
|
} : {
|
|
...r
|
|
};
|
|
if (!c.component && !c.loadComponent && (children || c.loadChildren) && c.outlet && c.outlet !== PRIMARY_OUTLET) {
|
|
c.component = ɵEmptyOutletComponent;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
function createRouterState(routeReuseStrategy, curr, prevState) {
|
|
const root = createNode(routeReuseStrategy, curr._root, prevState ? prevState._root : undefined);
|
|
return new RouterState(root, curr);
|
|
}
|
|
function createNode(routeReuseStrategy, curr, prevState) {
|
|
if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
|
|
const value = prevState.value;
|
|
value._futureSnapshot = curr.value;
|
|
const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);
|
|
return new TreeNode(value, children);
|
|
} else {
|
|
if (routeReuseStrategy.shouldAttach(curr.value)) {
|
|
const detachedRouteHandle = routeReuseStrategy.retrieve(curr.value);
|
|
if (detachedRouteHandle !== null) {
|
|
const tree = detachedRouteHandle.route;
|
|
tree.value._futureSnapshot = curr.value;
|
|
tree.children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
|
return tree;
|
|
}
|
|
}
|
|
const value = createActivatedRoute(curr.value);
|
|
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
|
return new TreeNode(value, children);
|
|
}
|
|
}
|
|
function createOrReuseChildren(routeReuseStrategy, curr, prevState) {
|
|
return curr.children.map(child => {
|
|
for (const p of prevState.children) {
|
|
if (routeReuseStrategy.shouldReuseRoute(child.value, p.value.snapshot)) {
|
|
return createNode(routeReuseStrategy, child, p);
|
|
}
|
|
}
|
|
return createNode(routeReuseStrategy, child);
|
|
});
|
|
}
|
|
function createActivatedRoute(c) {
|
|
return new ActivatedRoute(new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams), new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
|
|
}
|
|
|
|
class RedirectCommand {
|
|
redirectTo;
|
|
navigationBehaviorOptions;
|
|
constructor(redirectTo, navigationBehaviorOptions) {
|
|
this.redirectTo = redirectTo;
|
|
this.navigationBehaviorOptions = navigationBehaviorOptions;
|
|
}
|
|
}
|
|
|
|
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
|
|
function redirectingNavigationError(urlSerializer, redirect) {
|
|
const {
|
|
redirectTo,
|
|
navigationBehaviorOptions
|
|
} = isUrlTree(redirect) ? {
|
|
redirectTo: redirect,
|
|
navigationBehaviorOptions: undefined
|
|
} : redirect;
|
|
const error = navigationCancelingError(ngDevMode && `Redirecting to "${urlSerializer.serialize(redirectTo)}"`, NavigationCancellationCode.Redirect);
|
|
error.url = redirectTo;
|
|
error.navigationBehaviorOptions = navigationBehaviorOptions;
|
|
return error;
|
|
}
|
|
function navigationCancelingError(message, code) {
|
|
const error = new Error(`NavigationCancelingError: ${message || ''}`);
|
|
error[NAVIGATION_CANCELING_ERROR] = true;
|
|
error.cancellationCode = code;
|
|
return error;
|
|
}
|
|
function isRedirectingNavigationCancelingError(error) {
|
|
return isNavigationCancelingError(error) && isUrlTree(error.url);
|
|
}
|
|
function isNavigationCancelingError(error) {
|
|
return !!error && error[NAVIGATION_CANCELING_ERROR];
|
|
}
|
|
|
|
let warnedAboutUnsupportedInputBinding = false;
|
|
class ActivateRoutes {
|
|
routeReuseStrategy;
|
|
futureState;
|
|
currState;
|
|
forwardEvent;
|
|
inputBindingEnabled;
|
|
constructor(routeReuseStrategy, futureState, currState, forwardEvent, inputBindingEnabled) {
|
|
this.routeReuseStrategy = routeReuseStrategy;
|
|
this.futureState = futureState;
|
|
this.currState = currState;
|
|
this.forwardEvent = forwardEvent;
|
|
this.inputBindingEnabled = inputBindingEnabled;
|
|
}
|
|
activate(parentContexts) {
|
|
const futureRoot = this.futureState._root;
|
|
const currRoot = this.currState ? this.currState._root : null;
|
|
this.deactivateChildRoutes(futureRoot, currRoot, parentContexts);
|
|
advanceActivatedRoute(this.futureState.root);
|
|
this.activateChildRoutes(futureRoot, currRoot, parentContexts);
|
|
}
|
|
deactivateChildRoutes(futureNode, currNode, contexts) {
|
|
const children = nodeChildrenAsMap(currNode);
|
|
futureNode.children.forEach(futureChild => {
|
|
const childOutletName = futureChild.value.outlet;
|
|
this.deactivateRoutes(futureChild, children[childOutletName], contexts);
|
|
delete children[childOutletName];
|
|
});
|
|
Object.values(children).forEach(v => {
|
|
this.deactivateRouteAndItsChildren(v, contexts);
|
|
});
|
|
}
|
|
deactivateRoutes(futureNode, currNode, parentContext) {
|
|
const future = futureNode.value;
|
|
const curr = currNode ? currNode.value : null;
|
|
if (future === curr) {
|
|
if (future.component) {
|
|
const context = parentContext.getContext(future.outlet);
|
|
if (context) {
|
|
this.deactivateChildRoutes(futureNode, currNode, context.children);
|
|
}
|
|
} else {
|
|
this.deactivateChildRoutes(futureNode, currNode, parentContext);
|
|
}
|
|
} else {
|
|
if (curr) {
|
|
this.deactivateRouteAndItsChildren(currNode, parentContext);
|
|
}
|
|
}
|
|
}
|
|
deactivateRouteAndItsChildren(route, parentContexts) {
|
|
if (route.value.component && this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
|
|
this.detachAndStoreRouteSubtree(route, parentContexts);
|
|
} else {
|
|
this.deactivateRouteAndOutlet(route, parentContexts);
|
|
}
|
|
}
|
|
detachAndStoreRouteSubtree(route, parentContexts) {
|
|
const context = parentContexts.getContext(route.value.outlet);
|
|
const contexts = context && route.value.component ? context.children : parentContexts;
|
|
const children = nodeChildrenAsMap(route);
|
|
for (const treeNode of Object.values(children)) {
|
|
this.deactivateRouteAndItsChildren(treeNode, contexts);
|
|
}
|
|
if (context && context.outlet) {
|
|
const componentRef = context.outlet.detach();
|
|
const contexts = context.children.onOutletDeactivated();
|
|
this.routeReuseStrategy.store(route.value.snapshot, {
|
|
componentRef,
|
|
route,
|
|
contexts
|
|
});
|
|
}
|
|
}
|
|
deactivateRouteAndOutlet(route, parentContexts) {
|
|
const context = parentContexts.getContext(route.value.outlet);
|
|
const contexts = context && route.value.component ? context.children : parentContexts;
|
|
const children = nodeChildrenAsMap(route);
|
|
for (const treeNode of Object.values(children)) {
|
|
this.deactivateRouteAndItsChildren(treeNode, contexts);
|
|
}
|
|
if (context) {
|
|
if (context.outlet) {
|
|
context.outlet.deactivate();
|
|
context.children.onOutletDeactivated();
|
|
}
|
|
context.attachRef = null;
|
|
context.route = null;
|
|
}
|
|
}
|
|
activateChildRoutes(futureNode, currNode, contexts) {
|
|
const children = nodeChildrenAsMap(currNode);
|
|
futureNode.children.forEach(c => {
|
|
this.activateRoutes(c, children[c.value.outlet], contexts);
|
|
this.forwardEvent(new ActivationEnd(c.value.snapshot));
|
|
});
|
|
if (futureNode.children.length) {
|
|
this.forwardEvent(new ChildActivationEnd(futureNode.value.snapshot));
|
|
}
|
|
}
|
|
activateRoutes(futureNode, currNode, parentContexts) {
|
|
const future = futureNode.value;
|
|
const curr = currNode ? currNode.value : null;
|
|
advanceActivatedRoute(future);
|
|
if (future === curr) {
|
|
if (future.component) {
|
|
const context = parentContexts.getOrCreateContext(future.outlet);
|
|
this.activateChildRoutes(futureNode, currNode, context.children);
|
|
} else {
|
|
this.activateChildRoutes(futureNode, currNode, parentContexts);
|
|
}
|
|
} else {
|
|
if (future.component) {
|
|
const context = parentContexts.getOrCreateContext(future.outlet);
|
|
if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
|
|
const stored = this.routeReuseStrategy.retrieve(future.snapshot);
|
|
this.routeReuseStrategy.store(future.snapshot, null);
|
|
context.children.onOutletReAttached(stored.contexts);
|
|
context.attachRef = stored.componentRef;
|
|
context.route = stored.route.value;
|
|
if (context.outlet) {
|
|
context.outlet.attach(stored.componentRef, stored.route.value);
|
|
}
|
|
advanceActivatedRoute(stored.route.value);
|
|
this.activateChildRoutes(futureNode, null, context.children);
|
|
} else {
|
|
context.attachRef = null;
|
|
context.route = future;
|
|
if (context.outlet) {
|
|
context.outlet.activateWith(future, context.injector);
|
|
}
|
|
this.activateChildRoutes(futureNode, null, context.children);
|
|
}
|
|
} else {
|
|
this.activateChildRoutes(futureNode, null, parentContexts);
|
|
}
|
|
}
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
const context = parentContexts.getOrCreateContext(future.outlet);
|
|
const outlet = context.outlet;
|
|
if (outlet && this.inputBindingEnabled && !outlet.supportsBindingToComponentInputs && !warnedAboutUnsupportedInputBinding) {
|
|
console.warn(`'withComponentInputBinding' feature is enabled but ` + `this application is using an outlet that may not support binding to component inputs.`);
|
|
warnedAboutUnsupportedInputBinding = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class CanActivate {
|
|
path;
|
|
route;
|
|
constructor(path) {
|
|
this.path = path;
|
|
this.route = this.path[this.path.length - 1];
|
|
}
|
|
}
|
|
class CanDeactivate {
|
|
component;
|
|
route;
|
|
constructor(component, route) {
|
|
this.component = component;
|
|
this.route = route;
|
|
}
|
|
}
|
|
function getAllRouteGuards(future, curr, parentContexts) {
|
|
const futureRoot = future._root;
|
|
const currRoot = curr ? curr._root : null;
|
|
return getChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]);
|
|
}
|
|
function getCanActivateChild(p) {
|
|
const canActivateChild = p.routeConfig ? p.routeConfig.canActivateChild : null;
|
|
if (!canActivateChild || canActivateChild.length === 0) return null;
|
|
return {
|
|
node: p,
|
|
guards: canActivateChild
|
|
};
|
|
}
|
|
function getTokenOrFunctionIdentity(tokenOrFunction, injector) {
|
|
const NOT_FOUND = Symbol();
|
|
const result = injector.get(tokenOrFunction, NOT_FOUND);
|
|
if (result === NOT_FOUND) {
|
|
if (typeof tokenOrFunction === 'function' && !_isInjectable(tokenOrFunction)) {
|
|
return tokenOrFunction;
|
|
} else {
|
|
return injector.get(tokenOrFunction);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function getChildRouteGuards(futureNode, currNode, contexts, futurePath, checks = {
|
|
canDeactivateChecks: [],
|
|
canActivateChecks: []
|
|
}) {
|
|
const prevChildren = nodeChildrenAsMap(currNode);
|
|
futureNode.children.forEach(c => {
|
|
getRouteGuards(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]), checks);
|
|
delete prevChildren[c.value.outlet];
|
|
});
|
|
Object.entries(prevChildren).forEach(([k, v]) => deactivateRouteAndItsChildren(v, contexts.getContext(k), checks));
|
|
return checks;
|
|
}
|
|
function getRouteGuards(futureNode, currNode, parentContexts, futurePath, checks = {
|
|
canDeactivateChecks: [],
|
|
canActivateChecks: []
|
|
}) {
|
|
const future = futureNode.value;
|
|
const curr = currNode ? currNode.value : null;
|
|
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
|
|
if (curr && future.routeConfig === curr.routeConfig) {
|
|
const shouldRun = shouldRunGuardsAndResolvers(curr, future, future.routeConfig.runGuardsAndResolvers);
|
|
if (shouldRun) {
|
|
checks.canActivateChecks.push(new CanActivate(futurePath));
|
|
} else {
|
|
future.data = curr.data;
|
|
future._resolvedData = curr._resolvedData;
|
|
}
|
|
if (future.component) {
|
|
getChildRouteGuards(futureNode, currNode, context ? context.children : null, futurePath, checks);
|
|
} else {
|
|
getChildRouteGuards(futureNode, currNode, parentContexts, futurePath, checks);
|
|
}
|
|
if (shouldRun && context && context.outlet && context.outlet.isActivated) {
|
|
checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, curr));
|
|
}
|
|
} else {
|
|
if (curr) {
|
|
deactivateRouteAndItsChildren(currNode, context, checks);
|
|
}
|
|
checks.canActivateChecks.push(new CanActivate(futurePath));
|
|
if (future.component) {
|
|
getChildRouteGuards(futureNode, null, context ? context.children : null, futurePath, checks);
|
|
} else {
|
|
getChildRouteGuards(futureNode, null, parentContexts, futurePath, checks);
|
|
}
|
|
}
|
|
return checks;
|
|
}
|
|
function shouldRunGuardsAndResolvers(curr, future, mode) {
|
|
if (typeof mode === 'function') {
|
|
return runInInjectionContext(future._environmentInjector, () => mode(curr, future));
|
|
}
|
|
switch (mode) {
|
|
case 'pathParamsChange':
|
|
return !equalPath(curr.url, future.url);
|
|
case 'pathParamsOrQueryParamsChange':
|
|
return !equalPath(curr.url, future.url) || !shallowEqual(curr.queryParams, future.queryParams);
|
|
case 'always':
|
|
return true;
|
|
case 'paramsOrQueryParamsChange':
|
|
return !equalParamsAndUrlSegments(curr, future) || !shallowEqual(curr.queryParams, future.queryParams);
|
|
case 'paramsChange':
|
|
default:
|
|
return !equalParamsAndUrlSegments(curr, future);
|
|
}
|
|
}
|
|
function deactivateRouteAndItsChildren(route, context, checks) {
|
|
const children = nodeChildrenAsMap(route);
|
|
const r = route.value;
|
|
Object.entries(children).forEach(([childName, node]) => {
|
|
if (!r.component) {
|
|
deactivateRouteAndItsChildren(node, context, checks);
|
|
} else if (context) {
|
|
deactivateRouteAndItsChildren(node, context.children.getContext(childName), checks);
|
|
} else {
|
|
deactivateRouteAndItsChildren(node, null, checks);
|
|
}
|
|
});
|
|
if (!r.component) {
|
|
checks.canDeactivateChecks.push(new CanDeactivate(null, r));
|
|
} else if (context && context.outlet && context.outlet.isActivated) {
|
|
checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
|
|
} else {
|
|
checks.canDeactivateChecks.push(new CanDeactivate(null, r));
|
|
}
|
|
}
|
|
|
|
function isFunction(v) {
|
|
return typeof v === 'function';
|
|
}
|
|
function isBoolean(v) {
|
|
return typeof v === 'boolean';
|
|
}
|
|
function isCanLoad(guard) {
|
|
return guard && isFunction(guard.canLoad);
|
|
}
|
|
function isCanActivate(guard) {
|
|
return guard && isFunction(guard.canActivate);
|
|
}
|
|
function isCanActivateChild(guard) {
|
|
return guard && isFunction(guard.canActivateChild);
|
|
}
|
|
function isCanDeactivate(guard) {
|
|
return guard && isFunction(guard.canDeactivate);
|
|
}
|
|
function isCanMatch(guard) {
|
|
return guard && isFunction(guard.canMatch);
|
|
}
|
|
function isEmptyError(e) {
|
|
return e instanceof EmptyError || e?.name === 'EmptyError';
|
|
}
|
|
|
|
const INITIAL_VALUE = /* @__PURE__ */Symbol('INITIAL_VALUE');
|
|
function prioritizedGuardValue() {
|
|
return switchMap(obs => {
|
|
return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE)))).pipe(map(results => {
|
|
for (const result of results) {
|
|
if (result === true) {
|
|
continue;
|
|
} else if (result === INITIAL_VALUE) {
|
|
return INITIAL_VALUE;
|
|
} else if (result === false || isRedirect(result)) {
|
|
return result;
|
|
}
|
|
}
|
|
return true;
|
|
}), filter(item => item !== INITIAL_VALUE), take(1));
|
|
});
|
|
}
|
|
function isRedirect(val) {
|
|
return isUrlTree(val) || val instanceof RedirectCommand;
|
|
}
|
|
|
|
function abortSignalToObservable(signal) {
|
|
if (signal.aborted) {
|
|
return of(undefined).pipe(take(1));
|
|
}
|
|
return new Observable(subscriber => {
|
|
const handler = () => {
|
|
subscriber.next();
|
|
subscriber.complete();
|
|
};
|
|
signal.addEventListener('abort', handler);
|
|
return () => signal.removeEventListener('abort', handler);
|
|
});
|
|
}
|
|
function takeUntilAbort(signal) {
|
|
return takeUntil(abortSignalToObservable(signal));
|
|
}
|
|
|
|
function checkGuards(forwardEvent) {
|
|
return mergeMap(t => {
|
|
const {
|
|
targetSnapshot,
|
|
currentSnapshot,
|
|
guards: {
|
|
canActivateChecks,
|
|
canDeactivateChecks
|
|
}
|
|
} = t;
|
|
if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
|
|
return of({
|
|
...t,
|
|
guardsResult: true
|
|
});
|
|
}
|
|
return runCanDeactivateChecks(canDeactivateChecks, targetSnapshot, currentSnapshot).pipe(mergeMap(canDeactivate => {
|
|
return canDeactivate && isBoolean(canDeactivate) ? runCanActivateChecks(targetSnapshot, canActivateChecks, forwardEvent) : of(canDeactivate);
|
|
}), map(guardsResult => ({
|
|
...t,
|
|
guardsResult
|
|
})));
|
|
});
|
|
}
|
|
function runCanDeactivateChecks(checks, futureRSS, currRSS) {
|
|
return from(checks).pipe(mergeMap(check => runCanDeactivate(check.component, check.route, currRSS, futureRSS)), first(result => {
|
|
return result !== true;
|
|
}, true));
|
|
}
|
|
function runCanActivateChecks(futureSnapshot, checks, forwardEvent) {
|
|
return from(checks).pipe(concatMap(check => {
|
|
return concat(fireChildActivationStart(check.route.parent, forwardEvent), fireActivationStart(check.route, forwardEvent), runCanActivateChild(futureSnapshot, check.path), runCanActivate(futureSnapshot, check.route));
|
|
}), first(result => {
|
|
return result !== true;
|
|
}, true));
|
|
}
|
|
function fireActivationStart(snapshot, forwardEvent) {
|
|
if (snapshot !== null && forwardEvent) {
|
|
forwardEvent(new ActivationStart(snapshot));
|
|
}
|
|
return of(true);
|
|
}
|
|
function fireChildActivationStart(snapshot, forwardEvent) {
|
|
if (snapshot !== null && forwardEvent) {
|
|
forwardEvent(new ChildActivationStart(snapshot));
|
|
}
|
|
return of(true);
|
|
}
|
|
function runCanActivate(futureRSS, futureARS) {
|
|
const canActivate = futureARS.routeConfig ? futureARS.routeConfig.canActivate : null;
|
|
if (!canActivate || canActivate.length === 0) return of(true);
|
|
const canActivateObservables = canActivate.map(canActivate => {
|
|
return defer(() => {
|
|
const closestInjector = futureARS._environmentInjector;
|
|
const guard = getTokenOrFunctionIdentity(canActivate, closestInjector);
|
|
const guardVal = isCanActivate(guard) ? guard.canActivate(futureARS, futureRSS) : runInInjectionContext(closestInjector, () => guard(futureARS, futureRSS));
|
|
return wrapIntoObservable(guardVal).pipe(first());
|
|
});
|
|
});
|
|
return of(canActivateObservables).pipe(prioritizedGuardValue());
|
|
}
|
|
function runCanActivateChild(futureRSS, path) {
|
|
const futureARS = path[path.length - 1];
|
|
const canActivateChildGuards = path.slice(0, path.length - 1).reverse().map(p => getCanActivateChild(p)).filter(_ => _ !== null);
|
|
const canActivateChildGuardsMapped = canActivateChildGuards.map(d => {
|
|
return defer(() => {
|
|
const guardsMapped = d.guards.map(canActivateChild => {
|
|
const closestInjector = d.node._environmentInjector;
|
|
const guard = getTokenOrFunctionIdentity(canActivateChild, closestInjector);
|
|
const guardVal = isCanActivateChild(guard) ? guard.canActivateChild(futureARS, futureRSS) : runInInjectionContext(closestInjector, () => guard(futureARS, futureRSS));
|
|
return wrapIntoObservable(guardVal).pipe(first());
|
|
});
|
|
return of(guardsMapped).pipe(prioritizedGuardValue());
|
|
});
|
|
});
|
|
return of(canActivateChildGuardsMapped).pipe(prioritizedGuardValue());
|
|
}
|
|
function runCanDeactivate(component, currARS, currRSS, futureRSS) {
|
|
const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null;
|
|
if (!canDeactivate || canDeactivate.length === 0) return of(true);
|
|
const canDeactivateObservables = canDeactivate.map(c => {
|
|
const closestInjector = currARS._environmentInjector;
|
|
const guard = getTokenOrFunctionIdentity(c, closestInjector);
|
|
const guardVal = isCanDeactivate(guard) ? guard.canDeactivate(component, currARS, currRSS, futureRSS) : runInInjectionContext(closestInjector, () => guard(component, currARS, currRSS, futureRSS));
|
|
return wrapIntoObservable(guardVal).pipe(first());
|
|
});
|
|
return of(canDeactivateObservables).pipe(prioritizedGuardValue());
|
|
}
|
|
function runCanLoadGuards(injector, route, segments, urlSerializer, abortSignal) {
|
|
const canLoad = route.canLoad;
|
|
if (canLoad === undefined || canLoad.length === 0) {
|
|
return of(true);
|
|
}
|
|
const canLoadObservables = canLoad.map(injectionToken => {
|
|
const guard = getTokenOrFunctionIdentity(injectionToken, injector);
|
|
const guardVal = isCanLoad(guard) ? guard.canLoad(route, segments) : runInInjectionContext(injector, () => guard(route, segments));
|
|
const obs$ = wrapIntoObservable(guardVal);
|
|
return abortSignal ? obs$.pipe(takeUntilAbort(abortSignal)) : obs$;
|
|
});
|
|
return of(canLoadObservables).pipe(prioritizedGuardValue(), redirectIfUrlTree(urlSerializer));
|
|
}
|
|
function redirectIfUrlTree(urlSerializer) {
|
|
return pipe(tap(result => {
|
|
if (typeof result === 'boolean') return;
|
|
throw redirectingNavigationError(urlSerializer, result);
|
|
}), map(result => result === true));
|
|
}
|
|
function runCanMatchGuards(injector, route, segments, urlSerializer, abortSignal) {
|
|
const canMatch = route.canMatch;
|
|
if (!canMatch || canMatch.length === 0) return of(true);
|
|
const canMatchObservables = canMatch.map(injectionToken => {
|
|
const guard = getTokenOrFunctionIdentity(injectionToken, injector);
|
|
const guardVal = isCanMatch(guard) ? guard.canMatch(route, segments) : runInInjectionContext(injector, () => guard(route, segments));
|
|
return wrapIntoObservable(guardVal).pipe(takeUntilAbort(abortSignal));
|
|
});
|
|
return of(canMatchObservables).pipe(prioritizedGuardValue(), redirectIfUrlTree(urlSerializer));
|
|
}
|
|
|
|
class NoMatch extends Error {
|
|
segmentGroup;
|
|
constructor(segmentGroup) {
|
|
super();
|
|
this.segmentGroup = segmentGroup || null;
|
|
Object.setPrototypeOf(this, NoMatch.prototype);
|
|
}
|
|
}
|
|
class AbsoluteRedirect extends Error {
|
|
urlTree;
|
|
constructor(urlTree) {
|
|
super();
|
|
this.urlTree = urlTree;
|
|
Object.setPrototypeOf(this, AbsoluteRedirect.prototype);
|
|
}
|
|
}
|
|
function namedOutletsRedirect(redirectTo) {
|
|
throw new _RuntimeError(4000, (typeof ngDevMode === 'undefined' || ngDevMode) && `Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`);
|
|
}
|
|
function canLoadFails(route) {
|
|
throw navigationCancelingError((typeof ngDevMode === 'undefined' || ngDevMode) && `Cannot load children because the guard of the route "path: '${route.path}'" returned false`, NavigationCancellationCode.GuardRejected);
|
|
}
|
|
class ApplyRedirects {
|
|
urlSerializer;
|
|
urlTree;
|
|
constructor(urlSerializer, urlTree) {
|
|
this.urlSerializer = urlSerializer;
|
|
this.urlTree = urlTree;
|
|
}
|
|
async lineralizeSegments(route, urlTree) {
|
|
let res = [];
|
|
let c = urlTree.root;
|
|
while (true) {
|
|
res = res.concat(c.segments);
|
|
if (c.numberOfChildren === 0) {
|
|
return res;
|
|
}
|
|
if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
|
|
throw namedOutletsRedirect(`${route.redirectTo}`);
|
|
}
|
|
c = c.children[PRIMARY_OUTLET];
|
|
}
|
|
}
|
|
async applyRedirectCommands(segments, redirectTo, posParams, currentSnapshot, injector) {
|
|
const redirect = await getRedirectResult(redirectTo, currentSnapshot, injector);
|
|
if (redirect instanceof UrlTree) {
|
|
throw new AbsoluteRedirect(redirect);
|
|
}
|
|
const newTree = this.applyRedirectCreateUrlTree(redirect, this.urlSerializer.parse(redirect), segments, posParams);
|
|
if (redirect[0] === '/') {
|
|
throw new AbsoluteRedirect(newTree);
|
|
}
|
|
return newTree;
|
|
}
|
|
applyRedirectCreateUrlTree(redirectTo, urlTree, segments, posParams) {
|
|
const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
|
|
return new UrlTree(newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams), urlTree.fragment);
|
|
}
|
|
createQueryParams(redirectToParams, actualParams) {
|
|
const res = {};
|
|
Object.entries(redirectToParams).forEach(([k, v]) => {
|
|
const copySourceValue = typeof v === 'string' && v[0] === ':';
|
|
if (copySourceValue) {
|
|
const sourceName = v.substring(1);
|
|
res[k] = actualParams[sourceName];
|
|
} else {
|
|
res[k] = v;
|
|
}
|
|
});
|
|
return res;
|
|
}
|
|
createSegmentGroup(redirectTo, group, segments, posParams) {
|
|
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
|
|
let children = {};
|
|
Object.entries(group.children).forEach(([name, child]) => {
|
|
children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
|
|
});
|
|
return new UrlSegmentGroup(updatedSegments, children);
|
|
}
|
|
createSegments(redirectTo, redirectToSegments, actualSegments, posParams) {
|
|
return redirectToSegments.map(s => s.path[0] === ':' ? this.findPosParam(redirectTo, s, posParams) : this.findOrReturn(s, actualSegments));
|
|
}
|
|
findPosParam(redirectTo, redirectToUrlSegment, posParams) {
|
|
const pos = posParams[redirectToUrlSegment.path.substring(1)];
|
|
if (!pos) throw new _RuntimeError(4001, (typeof ngDevMode === 'undefined' || ngDevMode) && `Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
|
|
return pos;
|
|
}
|
|
findOrReturn(redirectToUrlSegment, actualSegments) {
|
|
let idx = 0;
|
|
for (const s of actualSegments) {
|
|
if (s.path === redirectToUrlSegment.path) {
|
|
actualSegments.splice(idx);
|
|
return s;
|
|
}
|
|
idx++;
|
|
}
|
|
return redirectToUrlSegment;
|
|
}
|
|
}
|
|
function getRedirectResult(redirectTo, currentSnapshot, injector) {
|
|
if (typeof redirectTo === 'string') {
|
|
return Promise.resolve(redirectTo);
|
|
}
|
|
const redirectToFn = redirectTo;
|
|
const {
|
|
queryParams,
|
|
fragment,
|
|
routeConfig,
|
|
url,
|
|
outlet,
|
|
params,
|
|
data,
|
|
title,
|
|
paramMap,
|
|
queryParamMap
|
|
} = currentSnapshot;
|
|
return firstValueFrom(wrapIntoObservable(runInInjectionContext(injector, () => redirectToFn({
|
|
params,
|
|
data,
|
|
queryParams,
|
|
fragment,
|
|
routeConfig,
|
|
url,
|
|
outlet,
|
|
title,
|
|
paramMap,
|
|
queryParamMap
|
|
}))));
|
|
}
|
|
|
|
function getOrCreateRouteInjectorIfNeeded(route, currentInjector) {
|
|
if (route.providers && !route._injector) {
|
|
route._injector = createEnvironmentInjector(route.providers, currentInjector, `Route: ${route.path}`);
|
|
}
|
|
return route._injector ?? currentInjector;
|
|
}
|
|
function validateConfig(config, parentPath = '', requireStandaloneComponents = false) {
|
|
for (let i = 0; i < config.length; i++) {
|
|
const route = config[i];
|
|
const fullPath = getFullPath(parentPath, route);
|
|
validateNode(route, fullPath, requireStandaloneComponents);
|
|
}
|
|
}
|
|
function assertStandalone(fullPath, component) {
|
|
if (component && _isNgModule(component)) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}'. You are using 'loadComponent' with a module, ` + `but it must be used with standalone components. Use 'loadChildren' instead.`);
|
|
} else if (component && !isStandalone(component)) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}'. The component must be standalone.`);
|
|
}
|
|
}
|
|
function validateNode(route, fullPath, requireStandaloneComponents) {
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
if (!route) {
|
|
throw new _RuntimeError(4014, `
|
|
Invalid configuration of route '${fullPath}': Encountered undefined route.
|
|
The reason might be an extra comma.
|
|
|
|
Example:
|
|
const routes: Routes = [
|
|
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
|
|
{ path: 'dashboard', component: DashboardComponent },, << two commas
|
|
{ path: 'detail/:id', component: HeroDetailComponent }
|
|
];
|
|
`);
|
|
}
|
|
if (Array.isArray(route)) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': Array cannot be specified`);
|
|
}
|
|
if (!route.redirectTo && !route.component && !route.loadComponent && !route.children && !route.loadChildren && route.outlet && route.outlet !== PRIMARY_OUTLET) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);
|
|
}
|
|
if (route.redirectTo && route.children) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': redirectTo and children cannot be used together`);
|
|
}
|
|
if (route.redirectTo && route.loadChildren) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': redirectTo and loadChildren cannot be used together`);
|
|
}
|
|
if (route.children && route.loadChildren) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': children and loadChildren cannot be used together`);
|
|
}
|
|
if (route.component && route.loadComponent) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': component and loadComponent cannot be used together`);
|
|
}
|
|
if (route.redirectTo) {
|
|
if (route.component || route.loadComponent) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': redirectTo and component/loadComponent cannot be used together`);
|
|
}
|
|
if (route.canMatch || route.canActivate) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': redirectTo and ${route.canMatch ? 'canMatch' : 'canActivate'} cannot be used together.` + `Redirects happen before guards are executed.`);
|
|
}
|
|
}
|
|
if (route.path && route.matcher) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);
|
|
}
|
|
if (route.redirectTo === void 0 && !route.component && !route.loadComponent && !route.children && !route.loadChildren) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}'. One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren`);
|
|
}
|
|
if (route.path === void 0 && route.matcher === void 0) {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': routes must have either a path or a matcher specified`);
|
|
}
|
|
if (typeof route.path === 'string' && route.path.charAt(0) === '/') {
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '${fullPath}': path cannot start with a slash`);
|
|
}
|
|
if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {
|
|
const exp = `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
|
|
throw new _RuntimeError(4014, `Invalid configuration of route '{path: "${fullPath}", redirectTo: "${route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
|
|
}
|
|
if (requireStandaloneComponents) {
|
|
assertStandalone(fullPath, route.component);
|
|
}
|
|
}
|
|
if (route.children) {
|
|
validateConfig(route.children, fullPath, requireStandaloneComponents);
|
|
}
|
|
}
|
|
function getFullPath(parentPath, currentRoute) {
|
|
if (!currentRoute) {
|
|
return parentPath;
|
|
}
|
|
if (!parentPath && !currentRoute.path) {
|
|
return '';
|
|
} else if (parentPath && !currentRoute.path) {
|
|
return `${parentPath}/`;
|
|
} else if (!parentPath && currentRoute.path) {
|
|
return currentRoute.path;
|
|
} else {
|
|
return `${parentPath}/${currentRoute.path}`;
|
|
}
|
|
}
|
|
function getOutlet(route) {
|
|
return route.outlet || PRIMARY_OUTLET;
|
|
}
|
|
function sortByMatchingOutlets(routes, outletName) {
|
|
const sortedConfig = routes.filter(r => getOutlet(r) === outletName);
|
|
sortedConfig.push(...routes.filter(r => getOutlet(r) !== outletName));
|
|
return sortedConfig;
|
|
}
|
|
|
|
const noMatch = {
|
|
matched: false,
|
|
consumedSegments: [],
|
|
remainingSegments: [],
|
|
parameters: {},
|
|
positionalParamSegments: {}
|
|
};
|
|
function matchWithChecks(segmentGroup, route, segments, injector, urlSerializer, abortSignal) {
|
|
const result = match(segmentGroup, route, segments);
|
|
if (!result.matched) {
|
|
return of(result);
|
|
}
|
|
injector = getOrCreateRouteInjectorIfNeeded(route, injector);
|
|
return runCanMatchGuards(injector, route, segments, urlSerializer, abortSignal).pipe(map(v => v === true ? result : {
|
|
...noMatch
|
|
}));
|
|
}
|
|
function match(segmentGroup, route, segments) {
|
|
if (route.path === '') {
|
|
if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
|
|
return {
|
|
...noMatch
|
|
};
|
|
}
|
|
return {
|
|
matched: true,
|
|
consumedSegments: [],
|
|
remainingSegments: segments,
|
|
parameters: {},
|
|
positionalParamSegments: {}
|
|
};
|
|
}
|
|
const matcher = route.matcher || defaultUrlMatcher;
|
|
const res = matcher(segments, segmentGroup, route);
|
|
if (!res) return {
|
|
...noMatch
|
|
};
|
|
const posParams = {};
|
|
Object.entries(res.posParams ?? {}).forEach(([k, v]) => {
|
|
posParams[k] = v.path;
|
|
});
|
|
const parameters = res.consumed.length > 0 ? {
|
|
...posParams,
|
|
...res.consumed[res.consumed.length - 1].parameters
|
|
} : posParams;
|
|
return {
|
|
matched: true,
|
|
consumedSegments: res.consumed,
|
|
remainingSegments: segments.slice(res.consumed.length),
|
|
parameters,
|
|
positionalParamSegments: res.posParams ?? {}
|
|
};
|
|
}
|
|
function split(segmentGroup, consumedSegments, slicedSegments, config) {
|
|
if (slicedSegments.length > 0 && containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) {
|
|
const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptyPaths(config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
|
return {
|
|
segmentGroup: s,
|
|
slicedSegments: []
|
|
};
|
|
}
|
|
if (slicedSegments.length === 0 && containsEmptyPathMatches(segmentGroup, slicedSegments, config)) {
|
|
const s = new UrlSegmentGroup(segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(segmentGroup, slicedSegments, config, segmentGroup.children));
|
|
return {
|
|
segmentGroup: s,
|
|
slicedSegments
|
|
};
|
|
}
|
|
const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children);
|
|
return {
|
|
segmentGroup: s,
|
|
slicedSegments
|
|
};
|
|
}
|
|
function addEmptyPathsToChildrenIfNeeded(segmentGroup, slicedSegments, routes, children) {
|
|
const res = {};
|
|
for (const r of routes) {
|
|
if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
|
|
const s = new UrlSegmentGroup([], {});
|
|
res[getOutlet(r)] = s;
|
|
}
|
|
}
|
|
return {
|
|
...children,
|
|
...res
|
|
};
|
|
}
|
|
function createChildrenForEmptyPaths(routes, primarySegment) {
|
|
const res = {};
|
|
res[PRIMARY_OUTLET] = primarySegment;
|
|
for (const r of routes) {
|
|
if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) {
|
|
const s = new UrlSegmentGroup([], {});
|
|
res[getOutlet(r)] = s;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) {
|
|
return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET);
|
|
}
|
|
function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) {
|
|
return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r));
|
|
}
|
|
function emptyPathMatch(segmentGroup, slicedSegments, r) {
|
|
if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') {
|
|
return false;
|
|
}
|
|
return r.path === '';
|
|
}
|
|
function noLeftoversInUrl(segmentGroup, segments, outlet) {
|
|
return segments.length === 0 && !segmentGroup.children[outlet];
|
|
}
|
|
|
|
class NoLeftoversInUrl {}
|
|
async function recognize$1(injector, configLoader, rootComponentType, config, urlTree, urlSerializer, paramsInheritanceStrategy = 'emptyOnly', abortSignal) {
|
|
return new Recognizer(injector, configLoader, rootComponentType, config, urlTree, paramsInheritanceStrategy, urlSerializer, abortSignal).recognize();
|
|
}
|
|
const MAX_ALLOWED_REDIRECTS = 31;
|
|
class Recognizer {
|
|
injector;
|
|
configLoader;
|
|
rootComponentType;
|
|
config;
|
|
urlTree;
|
|
paramsInheritanceStrategy;
|
|
urlSerializer;
|
|
abortSignal;
|
|
applyRedirects;
|
|
absoluteRedirectCount = 0;
|
|
allowRedirects = true;
|
|
constructor(injector, configLoader, rootComponentType, config, urlTree, paramsInheritanceStrategy, urlSerializer, abortSignal) {
|
|
this.injector = injector;
|
|
this.configLoader = configLoader;
|
|
this.rootComponentType = rootComponentType;
|
|
this.config = config;
|
|
this.urlTree = urlTree;
|
|
this.paramsInheritanceStrategy = paramsInheritanceStrategy;
|
|
this.urlSerializer = urlSerializer;
|
|
this.abortSignal = abortSignal;
|
|
this.applyRedirects = new ApplyRedirects(this.urlSerializer, this.urlTree);
|
|
}
|
|
noMatchError(e) {
|
|
return new _RuntimeError(4002, typeof ngDevMode === 'undefined' || ngDevMode ? `Cannot match any routes. URL Segment: '${e.segmentGroup}'` : `'${e.segmentGroup}'`);
|
|
}
|
|
async recognize() {
|
|
const rootSegmentGroup = split(this.urlTree.root, [], [], this.config).segmentGroup;
|
|
const {
|
|
children,
|
|
rootSnapshot
|
|
} = await this.match(rootSegmentGroup);
|
|
const rootNode = new TreeNode(rootSnapshot, children);
|
|
const routeState = new RouterStateSnapshot('', rootNode);
|
|
const tree = createUrlTreeFromSnapshot(rootSnapshot, [], this.urlTree.queryParams, this.urlTree.fragment);
|
|
tree.queryParams = this.urlTree.queryParams;
|
|
routeState.url = this.urlSerializer.serialize(tree);
|
|
return {
|
|
state: routeState,
|
|
tree
|
|
};
|
|
}
|
|
async match(rootSegmentGroup) {
|
|
const rootSnapshot = new ActivatedRouteSnapshot([], Object.freeze({}), Object.freeze({
|
|
...this.urlTree.queryParams
|
|
}), this.urlTree.fragment, Object.freeze({}), PRIMARY_OUTLET, this.rootComponentType, null, {}, this.injector);
|
|
try {
|
|
const children = await this.processSegmentGroup(this.injector, this.config, rootSegmentGroup, PRIMARY_OUTLET, rootSnapshot);
|
|
return {
|
|
children,
|
|
rootSnapshot
|
|
};
|
|
} catch (e) {
|
|
if (e instanceof AbsoluteRedirect) {
|
|
this.urlTree = e.urlTree;
|
|
return this.match(e.urlTree.root);
|
|
}
|
|
if (e instanceof NoMatch) {
|
|
throw this.noMatchError(e);
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
async processSegmentGroup(injector, config, segmentGroup, outlet, parentRoute) {
|
|
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
|
return this.processChildren(injector, config, segmentGroup, parentRoute);
|
|
}
|
|
const child = await this.processSegment(injector, config, segmentGroup, segmentGroup.segments, outlet, true, parentRoute);
|
|
return child instanceof TreeNode ? [child] : [];
|
|
}
|
|
async processChildren(injector, config, segmentGroup, parentRoute) {
|
|
const childOutlets = [];
|
|
for (const child of Object.keys(segmentGroup.children)) {
|
|
if (child === 'primary') {
|
|
childOutlets.unshift(child);
|
|
} else {
|
|
childOutlets.push(child);
|
|
}
|
|
}
|
|
let children = [];
|
|
for (const childOutlet of childOutlets) {
|
|
const child = segmentGroup.children[childOutlet];
|
|
const sortedConfig = sortByMatchingOutlets(config, childOutlet);
|
|
const outletChildren = await this.processSegmentGroup(injector, sortedConfig, child, childOutlet, parentRoute);
|
|
children.push(...outletChildren);
|
|
}
|
|
const mergedChildren = mergeEmptyPathMatches(children);
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
checkOutletNameUniqueness(mergedChildren);
|
|
}
|
|
sortActivatedRouteSnapshots(mergedChildren);
|
|
return mergedChildren;
|
|
}
|
|
async processSegment(injector, routes, segmentGroup, segments, outlet, allowRedirects, parentRoute) {
|
|
for (const r of routes) {
|
|
try {
|
|
return await this.processSegmentAgainstRoute(r._injector ?? injector, routes, r, segmentGroup, segments, outlet, allowRedirects, parentRoute);
|
|
} catch (e) {
|
|
if (e instanceof NoMatch || isEmptyError(e)) {
|
|
continue;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
|
return new NoLeftoversInUrl();
|
|
}
|
|
throw new NoMatch(segmentGroup);
|
|
}
|
|
async processSegmentAgainstRoute(injector, routes, route, rawSegment, segments, outlet, allowRedirects, parentRoute) {
|
|
if (getOutlet(route) !== outlet && (outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))) {
|
|
throw new NoMatch(rawSegment);
|
|
}
|
|
if (route.redirectTo === undefined) {
|
|
return this.matchSegmentAgainstRoute(injector, rawSegment, route, segments, outlet, parentRoute);
|
|
}
|
|
if (this.allowRedirects && allowRedirects) {
|
|
return this.expandSegmentAgainstRouteUsingRedirect(injector, rawSegment, routes, route, segments, outlet, parentRoute);
|
|
}
|
|
throw new NoMatch(rawSegment);
|
|
}
|
|
async expandSegmentAgainstRouteUsingRedirect(injector, segmentGroup, routes, route, segments, outlet, parentRoute) {
|
|
const {
|
|
matched,
|
|
parameters,
|
|
consumedSegments,
|
|
positionalParamSegments,
|
|
remainingSegments
|
|
} = match(segmentGroup, route, segments);
|
|
if (!matched) throw new NoMatch(segmentGroup);
|
|
if (typeof route.redirectTo === 'string' && route.redirectTo[0] === '/') {
|
|
this.absoluteRedirectCount++;
|
|
if (this.absoluteRedirectCount > MAX_ALLOWED_REDIRECTS) {
|
|
if (ngDevMode) {
|
|
throw new _RuntimeError(4016, `Detected possible infinite redirect when redirecting from '${this.urlTree}' to '${route.redirectTo}'.\n` + `This is currently a dev mode only error but will become a` + ` call stack size exceeded error in production in a future major version.`);
|
|
}
|
|
this.allowRedirects = false;
|
|
}
|
|
}
|
|
const currentSnapshot = new ActivatedRouteSnapshot(segments, parameters, Object.freeze({
|
|
...this.urlTree.queryParams
|
|
}), this.urlTree.fragment, getData(route), getOutlet(route), route.component ?? route._loadedComponent ?? null, route, getResolve(route), injector);
|
|
const inherited = getInherited(currentSnapshot, parentRoute, this.paramsInheritanceStrategy);
|
|
currentSnapshot.params = Object.freeze(inherited.params);
|
|
currentSnapshot.data = Object.freeze(inherited.data);
|
|
if (this.abortSignal.aborted) {
|
|
throw new Error(this.abortSignal.reason);
|
|
}
|
|
const newTree = await this.applyRedirects.applyRedirectCommands(consumedSegments, route.redirectTo, positionalParamSegments, currentSnapshot, injector);
|
|
const newSegments = await this.applyRedirects.lineralizeSegments(route, newTree);
|
|
return this.processSegment(injector, routes, segmentGroup, newSegments.concat(remainingSegments), outlet, false, parentRoute);
|
|
}
|
|
async matchSegmentAgainstRoute(injector, rawSegment, route, segments, outlet, parentRoute) {
|
|
if (this.abortSignal.aborted) {
|
|
throw new Error(this.abortSignal.reason);
|
|
}
|
|
const result = await firstValueFrom(matchWithChecks(rawSegment, route, segments, injector, this.urlSerializer, this.abortSignal));
|
|
if (route.path === '**') {
|
|
rawSegment.children = {};
|
|
}
|
|
if (!result?.matched) {
|
|
throw new NoMatch(rawSegment);
|
|
}
|
|
injector = route._injector ?? injector;
|
|
const {
|
|
routes: childConfig
|
|
} = await this.getChildConfig(injector, route, segments);
|
|
const childInjector = route._loadedInjector ?? injector;
|
|
const {
|
|
parameters,
|
|
consumedSegments,
|
|
remainingSegments
|
|
} = result;
|
|
const snapshot = new ActivatedRouteSnapshot(consumedSegments, parameters, Object.freeze({
|
|
...this.urlTree.queryParams
|
|
}), this.urlTree.fragment, getData(route), getOutlet(route), route.component ?? route._loadedComponent ?? null, route, getResolve(route), injector);
|
|
const inherited = getInherited(snapshot, parentRoute, this.paramsInheritanceStrategy);
|
|
snapshot.params = Object.freeze(inherited.params);
|
|
snapshot.data = Object.freeze(inherited.data);
|
|
const {
|
|
segmentGroup,
|
|
slicedSegments
|
|
} = split(rawSegment, consumedSegments, remainingSegments, childConfig);
|
|
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
|
const children = await this.processChildren(childInjector, childConfig, segmentGroup, snapshot);
|
|
return new TreeNode(snapshot, children);
|
|
}
|
|
if (childConfig.length === 0 && slicedSegments.length === 0) {
|
|
return new TreeNode(snapshot, []);
|
|
}
|
|
const matchedOnOutlet = getOutlet(route) === outlet;
|
|
const child = await this.processSegment(childInjector, childConfig, segmentGroup, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet, true, snapshot);
|
|
return new TreeNode(snapshot, child instanceof TreeNode ? [child] : []);
|
|
}
|
|
async getChildConfig(injector, route, segments) {
|
|
if (route.children) {
|
|
return {
|
|
routes: route.children,
|
|
injector
|
|
};
|
|
}
|
|
if (route.loadChildren) {
|
|
if (route._loadedRoutes !== undefined) {
|
|
const ngModuleFactory = route._loadedNgModuleFactory;
|
|
if (ngModuleFactory && !route._loadedInjector) {
|
|
route._loadedInjector = ngModuleFactory.create(injector).injector;
|
|
}
|
|
return {
|
|
routes: route._loadedRoutes,
|
|
injector: route._loadedInjector
|
|
};
|
|
}
|
|
if (this.abortSignal.aborted) {
|
|
throw new Error(this.abortSignal.reason);
|
|
}
|
|
const shouldLoadResult = await firstValueFrom(runCanLoadGuards(injector, route, segments, this.urlSerializer, this.abortSignal));
|
|
if (shouldLoadResult) {
|
|
const cfg = await this.configLoader.loadChildren(injector, route);
|
|
route._loadedRoutes = cfg.routes;
|
|
route._loadedInjector = cfg.injector;
|
|
route._loadedNgModuleFactory = cfg.factory;
|
|
return cfg;
|
|
}
|
|
throw canLoadFails(route);
|
|
}
|
|
return {
|
|
routes: [],
|
|
injector
|
|
};
|
|
}
|
|
}
|
|
function sortActivatedRouteSnapshots(nodes) {
|
|
nodes.sort((a, b) => {
|
|
if (a.value.outlet === PRIMARY_OUTLET) return -1;
|
|
if (b.value.outlet === PRIMARY_OUTLET) return 1;
|
|
return a.value.outlet.localeCompare(b.value.outlet);
|
|
});
|
|
}
|
|
function hasEmptyPathConfig(node) {
|
|
const config = node.value.routeConfig;
|
|
return config && config.path === '';
|
|
}
|
|
function mergeEmptyPathMatches(nodes) {
|
|
const result = [];
|
|
const mergedNodes = new Set();
|
|
for (const node of nodes) {
|
|
if (!hasEmptyPathConfig(node)) {
|
|
result.push(node);
|
|
continue;
|
|
}
|
|
const duplicateEmptyPathNode = result.find(resultNode => node.value.routeConfig === resultNode.value.routeConfig);
|
|
if (duplicateEmptyPathNode !== undefined) {
|
|
duplicateEmptyPathNode.children.push(...node.children);
|
|
mergedNodes.add(duplicateEmptyPathNode);
|
|
} else {
|
|
result.push(node);
|
|
}
|
|
}
|
|
for (const mergedNode of mergedNodes) {
|
|
const mergedChildren = mergeEmptyPathMatches(mergedNode.children);
|
|
result.push(new TreeNode(mergedNode.value, mergedChildren));
|
|
}
|
|
return result.filter(n => !mergedNodes.has(n));
|
|
}
|
|
function checkOutletNameUniqueness(nodes) {
|
|
const names = {};
|
|
nodes.forEach(n => {
|
|
const routeWithSameOutletName = names[n.value.outlet];
|
|
if (routeWithSameOutletName) {
|
|
const p = routeWithSameOutletName.url.map(s => s.toString()).join('/');
|
|
const c = n.value.url.map(s => s.toString()).join('/');
|
|
throw new _RuntimeError(4006, (typeof ngDevMode === 'undefined' || ngDevMode) && `Two segments cannot have the same outlet name: '${p}' and '${c}'.`);
|
|
}
|
|
names[n.value.outlet] = n.value;
|
|
});
|
|
}
|
|
function getData(route) {
|
|
return route.data || {};
|
|
}
|
|
function getResolve(route) {
|
|
return route.resolve || {};
|
|
}
|
|
|
|
function recognize(injector, configLoader, rootComponentType, config, serializer, paramsInheritanceStrategy, abortSignal) {
|
|
return mergeMap(async t => {
|
|
const {
|
|
state: targetSnapshot,
|
|
tree: urlAfterRedirects
|
|
} = await recognize$1(injector, configLoader, rootComponentType, config, t.extractedUrl, serializer, paramsInheritanceStrategy, abortSignal);
|
|
return {
|
|
...t,
|
|
targetSnapshot,
|
|
urlAfterRedirects
|
|
};
|
|
});
|
|
}
|
|
|
|
function resolveData(paramsInheritanceStrategy) {
|
|
return mergeMap(t => {
|
|
const {
|
|
targetSnapshot,
|
|
guards: {
|
|
canActivateChecks
|
|
}
|
|
} = t;
|
|
if (!canActivateChecks.length) {
|
|
return of(t);
|
|
}
|
|
const routesWithResolversToRun = new Set(canActivateChecks.map(check => check.route));
|
|
const routesNeedingDataUpdates = new Set();
|
|
for (const route of routesWithResolversToRun) {
|
|
if (routesNeedingDataUpdates.has(route)) {
|
|
continue;
|
|
}
|
|
for (const newRoute of flattenRouteTree(route)) {
|
|
routesNeedingDataUpdates.add(newRoute);
|
|
}
|
|
}
|
|
let routesProcessed = 0;
|
|
return from(routesNeedingDataUpdates).pipe(concatMap(route => {
|
|
if (routesWithResolversToRun.has(route)) {
|
|
return runResolve(route, targetSnapshot, paramsInheritanceStrategy);
|
|
} else {
|
|
route.data = getInherited(route, route.parent, paramsInheritanceStrategy).resolve;
|
|
return of(void 0);
|
|
}
|
|
}), tap(() => routesProcessed++), takeLast(1), mergeMap(_ => routesProcessed === routesNeedingDataUpdates.size ? of(t) : EMPTY));
|
|
});
|
|
}
|
|
function flattenRouteTree(route) {
|
|
const descendants = route.children.map(child => flattenRouteTree(child)).flat();
|
|
return [route, ...descendants];
|
|
}
|
|
function runResolve(futureARS, futureRSS, paramsInheritanceStrategy) {
|
|
const config = futureARS.routeConfig;
|
|
const resolve = futureARS._resolve;
|
|
if (config?.title !== undefined && !hasStaticTitle(config)) {
|
|
resolve[RouteTitleKey] = config.title;
|
|
}
|
|
return defer(() => {
|
|
futureARS.data = getInherited(futureARS, futureARS.parent, paramsInheritanceStrategy).resolve;
|
|
return resolveNode(resolve, futureARS, futureRSS).pipe(map(resolvedData => {
|
|
futureARS._resolvedData = resolvedData;
|
|
futureARS.data = {
|
|
...futureARS.data,
|
|
...resolvedData
|
|
};
|
|
return null;
|
|
}));
|
|
});
|
|
}
|
|
function resolveNode(resolve, futureARS, futureRSS) {
|
|
const keys = getDataKeys(resolve);
|
|
if (keys.length === 0) {
|
|
return of({});
|
|
}
|
|
const data = {};
|
|
return from(keys).pipe(mergeMap(key => getResolver(resolve[key], futureARS, futureRSS).pipe(first(), tap(value => {
|
|
if (value instanceof RedirectCommand) {
|
|
throw redirectingNavigationError(new DefaultUrlSerializer(), value);
|
|
}
|
|
data[key] = value;
|
|
}))), takeLast(1), map(() => data), catchError(e => isEmptyError(e) ? EMPTY : throwError(e)));
|
|
}
|
|
function getResolver(injectionToken, futureARS, futureRSS) {
|
|
const closestInjector = futureARS._environmentInjector;
|
|
const resolver = getTokenOrFunctionIdentity(injectionToken, closestInjector);
|
|
const resolverValue = resolver.resolve ? resolver.resolve(futureARS, futureRSS) : runInInjectionContext(closestInjector, () => resolver(futureARS, futureRSS));
|
|
return wrapIntoObservable(resolverValue);
|
|
}
|
|
|
|
function switchTap(next) {
|
|
return switchMap(v => {
|
|
const nextResult = next(v);
|
|
if (nextResult) {
|
|
return from(nextResult).pipe(map(() => v));
|
|
}
|
|
return of(v);
|
|
});
|
|
}
|
|
|
|
class TitleStrategy {
|
|
buildTitle(snapshot) {
|
|
let pageTitle;
|
|
let route = snapshot.root;
|
|
while (route !== undefined) {
|
|
pageTitle = this.getResolvedTitleForRoute(route) ?? pageTitle;
|
|
route = route.children.find(child => child.outlet === PRIMARY_OUTLET);
|
|
}
|
|
return pageTitle;
|
|
}
|
|
getResolvedTitleForRoute(snapshot) {
|
|
return snapshot.data[RouteTitleKey];
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: TitleStrategy,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: TitleStrategy,
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultTitleStrategy)
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: TitleStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultTitleStrategy)
|
|
}]
|
|
}]
|
|
});
|
|
class DefaultTitleStrategy extends TitleStrategy {
|
|
title;
|
|
constructor(title) {
|
|
super();
|
|
this.title = title;
|
|
}
|
|
updateTitle(snapshot) {
|
|
const title = this.buildTitle(snapshot);
|
|
if (title !== undefined) {
|
|
this.title.setTitle(title);
|
|
}
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultTitleStrategy,
|
|
deps: [{
|
|
token: i1.Title
|
|
}],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultTitleStrategy,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultTitleStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}],
|
|
ctorParameters: () => [{
|
|
type: i1.Title
|
|
}]
|
|
});
|
|
|
|
const ROUTER_CONFIGURATION = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'router config' : '', {
|
|
factory: () => ({})
|
|
});
|
|
|
|
const ROUTES = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ROUTES' : '');
|
|
class RouterConfigLoader {
|
|
componentLoaders = new WeakMap();
|
|
childrenLoaders = new WeakMap();
|
|
onLoadStartListener;
|
|
onLoadEndListener;
|
|
compiler = inject(Compiler);
|
|
async loadComponent(injector, route) {
|
|
if (this.componentLoaders.get(route)) {
|
|
return this.componentLoaders.get(route);
|
|
} else if (route._loadedComponent) {
|
|
return Promise.resolve(route._loadedComponent);
|
|
}
|
|
if (this.onLoadStartListener) {
|
|
this.onLoadStartListener(route);
|
|
}
|
|
const loader = (async () => {
|
|
try {
|
|
const loaded = await wrapIntoPromise(runInInjectionContext(injector, () => route.loadComponent()));
|
|
const component = await maybeResolveResources(maybeUnwrapDefaultExport(loaded));
|
|
if (this.onLoadEndListener) {
|
|
this.onLoadEndListener(route);
|
|
}
|
|
(typeof ngDevMode === 'undefined' || ngDevMode) && assertStandalone(route.path ?? '', component);
|
|
route._loadedComponent = component;
|
|
return component;
|
|
} finally {
|
|
this.componentLoaders.delete(route);
|
|
}
|
|
})();
|
|
this.componentLoaders.set(route, loader);
|
|
return loader;
|
|
}
|
|
loadChildren(parentInjector, route) {
|
|
if (this.childrenLoaders.get(route)) {
|
|
return this.childrenLoaders.get(route);
|
|
} else if (route._loadedRoutes) {
|
|
return Promise.resolve({
|
|
routes: route._loadedRoutes,
|
|
injector: route._loadedInjector
|
|
});
|
|
}
|
|
if (this.onLoadStartListener) {
|
|
this.onLoadStartListener(route);
|
|
}
|
|
const loader = (async () => {
|
|
try {
|
|
const result = await loadChildren(route, this.compiler, parentInjector, this.onLoadEndListener);
|
|
route._loadedRoutes = result.routes;
|
|
route._loadedInjector = result.injector;
|
|
route._loadedNgModuleFactory = result.factory;
|
|
return result;
|
|
} finally {
|
|
this.childrenLoaders.delete(route);
|
|
}
|
|
})();
|
|
this.childrenLoaders.set(route, loader);
|
|
return loader;
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouterConfigLoader,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouterConfigLoader,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouterConfigLoader,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}]
|
|
});
|
|
async function loadChildren(route, compiler, parentInjector, onLoadEndListener) {
|
|
const loaded = await wrapIntoPromise(runInInjectionContext(parentInjector, () => route.loadChildren()));
|
|
const t = await maybeResolveResources(maybeUnwrapDefaultExport(loaded));
|
|
let factoryOrRoutes;
|
|
if (t instanceof NgModuleFactory || Array.isArray(t)) {
|
|
factoryOrRoutes = t;
|
|
} else {
|
|
factoryOrRoutes = await compiler.compileModuleAsync(t);
|
|
}
|
|
if (onLoadEndListener) {
|
|
onLoadEndListener(route);
|
|
}
|
|
let injector;
|
|
let rawRoutes;
|
|
let requireStandaloneComponents = false;
|
|
let factory = undefined;
|
|
if (Array.isArray(factoryOrRoutes)) {
|
|
rawRoutes = factoryOrRoutes;
|
|
requireStandaloneComponents = true;
|
|
} else {
|
|
injector = factoryOrRoutes.create(parentInjector).injector;
|
|
factory = factoryOrRoutes;
|
|
rawRoutes = injector.get(ROUTES, [], {
|
|
optional: true,
|
|
self: true
|
|
}).flat();
|
|
}
|
|
const routes = rawRoutes.map(standardizeConfig);
|
|
(typeof ngDevMode === 'undefined' || ngDevMode) && validateConfig(routes, route.path, requireStandaloneComponents);
|
|
return {
|
|
routes,
|
|
injector,
|
|
factory
|
|
};
|
|
}
|
|
function isWrappedDefaultExport(value) {
|
|
return value && typeof value === 'object' && 'default' in value;
|
|
}
|
|
function maybeUnwrapDefaultExport(input) {
|
|
return isWrappedDefaultExport(input) ? input['default'] : input;
|
|
}
|
|
async function maybeResolveResources(value) {
|
|
if ((typeof ngJitMode === 'undefined' || ngJitMode) && typeof fetch === 'function') {
|
|
try {
|
|
await _resolveComponentResources(fetch);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
class UrlHandlingStrategy {
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlHandlingStrategy,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlHandlingStrategy,
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultUrlHandlingStrategy)
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: UrlHandlingStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultUrlHandlingStrategy)
|
|
}]
|
|
}]
|
|
});
|
|
class DefaultUrlHandlingStrategy {
|
|
shouldProcessUrl(url) {
|
|
return true;
|
|
}
|
|
extract(url) {
|
|
return url;
|
|
}
|
|
merge(newUrlPart, wholeUrl) {
|
|
return newUrlPart;
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultUrlHandlingStrategy,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultUrlHandlingStrategy,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultUrlHandlingStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}]
|
|
});
|
|
|
|
const CREATE_VIEW_TRANSITION = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'view transition helper' : '');
|
|
const VIEW_TRANSITION_OPTIONS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'view transition options' : '');
|
|
function createViewTransition(injector, from, to) {
|
|
const transitionOptions = injector.get(VIEW_TRANSITION_OPTIONS);
|
|
const document = injector.get(DOCUMENT);
|
|
if (!document.startViewTransition || transitionOptions.skipNextTransition) {
|
|
transitionOptions.skipNextTransition = false;
|
|
return new Promise(resolve => setTimeout(resolve));
|
|
}
|
|
let resolveViewTransitionStarted;
|
|
const viewTransitionStarted = new Promise(resolve => {
|
|
resolveViewTransitionStarted = resolve;
|
|
});
|
|
const transition = document.startViewTransition(() => {
|
|
resolveViewTransitionStarted();
|
|
return createRenderPromise(injector);
|
|
});
|
|
transition.updateCallbackDone.catch(error => {
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
console.error(error);
|
|
}
|
|
});
|
|
transition.ready.catch(error => {
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
console.error(error);
|
|
}
|
|
});
|
|
transition.finished.catch(error => {
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
console.error(error);
|
|
}
|
|
});
|
|
const {
|
|
onViewTransitionCreated
|
|
} = transitionOptions;
|
|
if (onViewTransitionCreated) {
|
|
runInInjectionContext(injector, () => onViewTransitionCreated({
|
|
transition,
|
|
from,
|
|
to
|
|
}));
|
|
}
|
|
return viewTransitionStarted;
|
|
}
|
|
function createRenderPromise(injector) {
|
|
return new Promise(resolve => {
|
|
afterNextRender({
|
|
read: () => setTimeout(resolve)
|
|
}, {
|
|
injector
|
|
});
|
|
});
|
|
}
|
|
|
|
const noop = () => {};
|
|
const NAVIGATION_ERROR_HANDLER = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'navigation error handler' : '');
|
|
class NavigationTransitions {
|
|
currentNavigation = signal(null, {
|
|
...(ngDevMode ? {
|
|
debugName: "currentNavigation"
|
|
} : {}),
|
|
equal: () => false
|
|
});
|
|
currentTransition = null;
|
|
lastSuccessfulNavigation = signal(null, ...(ngDevMode ? [{
|
|
debugName: "lastSuccessfulNavigation"
|
|
}] : []));
|
|
events = new Subject();
|
|
transitionAbortWithErrorSubject = new Subject();
|
|
configLoader = inject(RouterConfigLoader);
|
|
environmentInjector = inject(EnvironmentInjector);
|
|
destroyRef = inject(DestroyRef);
|
|
urlSerializer = inject(UrlSerializer);
|
|
rootContexts = inject(ChildrenOutletContexts);
|
|
location = inject(Location);
|
|
inputBindingEnabled = inject(INPUT_BINDER, {
|
|
optional: true
|
|
}) !== null;
|
|
titleStrategy = inject(TitleStrategy);
|
|
options = inject(ROUTER_CONFIGURATION, {
|
|
optional: true
|
|
}) || {};
|
|
paramsInheritanceStrategy = this.options.paramsInheritanceStrategy || 'emptyOnly';
|
|
urlHandlingStrategy = inject(UrlHandlingStrategy);
|
|
createViewTransition = inject(CREATE_VIEW_TRANSITION, {
|
|
optional: true
|
|
});
|
|
navigationErrorHandler = inject(NAVIGATION_ERROR_HANDLER, {
|
|
optional: true
|
|
});
|
|
navigationId = 0;
|
|
get hasRequestedNavigation() {
|
|
return this.navigationId !== 0;
|
|
}
|
|
transitions;
|
|
afterPreactivation = () => of(void 0);
|
|
rootComponentType = null;
|
|
destroyed = false;
|
|
constructor() {
|
|
const onLoadStart = r => this.events.next(new RouteConfigLoadStart(r));
|
|
const onLoadEnd = r => this.events.next(new RouteConfigLoadEnd(r));
|
|
this.configLoader.onLoadEndListener = onLoadEnd;
|
|
this.configLoader.onLoadStartListener = onLoadStart;
|
|
this.destroyRef.onDestroy(() => {
|
|
this.destroyed = true;
|
|
});
|
|
}
|
|
complete() {
|
|
this.transitions?.complete();
|
|
}
|
|
handleNavigationRequest(request) {
|
|
const id = ++this.navigationId;
|
|
untracked(() => {
|
|
this.transitions?.next({
|
|
...request,
|
|
extractedUrl: this.urlHandlingStrategy.extract(request.rawUrl),
|
|
targetSnapshot: null,
|
|
targetRouterState: null,
|
|
guards: {
|
|
canActivateChecks: [],
|
|
canDeactivateChecks: []
|
|
},
|
|
guardsResult: null,
|
|
id
|
|
});
|
|
});
|
|
}
|
|
setupNavigations(router) {
|
|
this.transitions = new BehaviorSubject(null);
|
|
return this.transitions.pipe(filter(t => t !== null), switchMap(overallTransitionState => {
|
|
let completedOrAborted = false;
|
|
const abortController = new AbortController();
|
|
const shouldContinueNavigation = () => {
|
|
return !completedOrAborted && this.currentTransition?.id === overallTransitionState.id;
|
|
};
|
|
return of(overallTransitionState).pipe(switchMap(t => {
|
|
if (this.navigationId > overallTransitionState.id) {
|
|
const cancellationReason = typeof ngDevMode === 'undefined' || ngDevMode ? `Navigation ID ${overallTransitionState.id} is not equal to the current navigation id ${this.navigationId}` : '';
|
|
this.cancelNavigationTransition(overallTransitionState, cancellationReason, NavigationCancellationCode.SupersededByNewNavigation);
|
|
return EMPTY;
|
|
}
|
|
this.currentTransition = overallTransitionState;
|
|
const lastSuccessfulNavigation = this.lastSuccessfulNavigation();
|
|
this.currentNavigation.set({
|
|
id: t.id,
|
|
initialUrl: t.rawUrl,
|
|
extractedUrl: t.extractedUrl,
|
|
targetBrowserUrl: typeof t.extras.browserUrl === 'string' ? this.urlSerializer.parse(t.extras.browserUrl) : t.extras.browserUrl,
|
|
trigger: t.source,
|
|
extras: t.extras,
|
|
previousNavigation: !lastSuccessfulNavigation ? null : {
|
|
...lastSuccessfulNavigation,
|
|
previousNavigation: null
|
|
},
|
|
abort: () => abortController.abort()
|
|
});
|
|
const urlTransition = !router.navigated || this.isUpdatingInternalState() || this.isUpdatedBrowserUrl();
|
|
const onSameUrlNavigation = t.extras.onSameUrlNavigation ?? router.onSameUrlNavigation;
|
|
if (!urlTransition && onSameUrlNavigation !== 'reload') {
|
|
const reason = typeof ngDevMode === 'undefined' || ngDevMode ? `Navigation to ${t.rawUrl} was ignored because it is the same as the current Router URL.` : '';
|
|
this.events.next(new NavigationSkipped(t.id, this.urlSerializer.serialize(t.rawUrl), reason, NavigationSkippedCode.IgnoredSameUrlNavigation));
|
|
t.resolve(false);
|
|
return EMPTY;
|
|
}
|
|
if (this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl)) {
|
|
return of(t).pipe(switchMap(t => {
|
|
this.events.next(new NavigationStart(t.id, this.urlSerializer.serialize(t.extractedUrl), t.source, t.restoredState));
|
|
if (t.id !== this.navigationId) {
|
|
return EMPTY;
|
|
}
|
|
return Promise.resolve(t);
|
|
}), recognize(this.environmentInjector, this.configLoader, this.rootComponentType, router.config, this.urlSerializer, this.paramsInheritanceStrategy, abortController.signal), tap(t => {
|
|
overallTransitionState.targetSnapshot = t.targetSnapshot;
|
|
overallTransitionState.urlAfterRedirects = t.urlAfterRedirects;
|
|
this.currentNavigation.update(nav => {
|
|
nav.finalUrl = t.urlAfterRedirects;
|
|
return nav;
|
|
});
|
|
const routesRecognized = new RoutesRecognized(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
|
this.events.next(routesRecognized);
|
|
}));
|
|
} else if (urlTransition && this.urlHandlingStrategy.shouldProcessUrl(t.currentRawUrl)) {
|
|
const {
|
|
id,
|
|
extractedUrl,
|
|
source,
|
|
restoredState,
|
|
extras
|
|
} = t;
|
|
const navStart = new NavigationStart(id, this.urlSerializer.serialize(extractedUrl), source, restoredState);
|
|
this.events.next(navStart);
|
|
const targetSnapshot = createEmptyState(this.rootComponentType, this.environmentInjector).snapshot;
|
|
this.currentTransition = overallTransitionState = {
|
|
...t,
|
|
targetSnapshot,
|
|
urlAfterRedirects: extractedUrl,
|
|
extras: {
|
|
...extras,
|
|
skipLocationChange: false,
|
|
replaceUrl: false
|
|
}
|
|
};
|
|
this.currentNavigation.update(nav => {
|
|
nav.finalUrl = extractedUrl;
|
|
return nav;
|
|
});
|
|
return of(overallTransitionState);
|
|
} else {
|
|
const reason = typeof ngDevMode === 'undefined' || ngDevMode ? `Navigation was ignored because the UrlHandlingStrategy` + ` indicated neither the current URL ${t.currentRawUrl} nor target URL ${t.rawUrl} should be processed.` : '';
|
|
this.events.next(new NavigationSkipped(t.id, this.urlSerializer.serialize(t.extractedUrl), reason, NavigationSkippedCode.IgnoredByUrlHandlingStrategy));
|
|
t.resolve(false);
|
|
return EMPTY;
|
|
}
|
|
}), map(t => {
|
|
const guardsStart = new GuardsCheckStart(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
|
this.events.next(guardsStart);
|
|
this.currentTransition = overallTransitionState = {
|
|
...t,
|
|
guards: getAllRouteGuards(t.targetSnapshot, t.currentSnapshot, this.rootContexts)
|
|
};
|
|
return overallTransitionState;
|
|
}), checkGuards(evt => this.events.next(evt)), switchMap(t => {
|
|
overallTransitionState.guardsResult = t.guardsResult;
|
|
if (t.guardsResult && typeof t.guardsResult !== 'boolean') {
|
|
throw redirectingNavigationError(this.urlSerializer, t.guardsResult);
|
|
}
|
|
const guardsEnd = new GuardsCheckEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot, !!t.guardsResult);
|
|
this.events.next(guardsEnd);
|
|
if (!shouldContinueNavigation()) {
|
|
return EMPTY;
|
|
}
|
|
if (!t.guardsResult) {
|
|
this.cancelNavigationTransition(t, '', NavigationCancellationCode.GuardRejected);
|
|
return EMPTY;
|
|
}
|
|
if (t.guards.canActivateChecks.length === 0) {
|
|
return of(t);
|
|
}
|
|
const resolveStart = new ResolveStart(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
|
this.events.next(resolveStart);
|
|
if (!shouldContinueNavigation()) {
|
|
return EMPTY;
|
|
}
|
|
let dataResolved = false;
|
|
return of(t).pipe(resolveData(this.paramsInheritanceStrategy), tap({
|
|
next: () => {
|
|
dataResolved = true;
|
|
const resolveEnd = new ResolveEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
|
this.events.next(resolveEnd);
|
|
},
|
|
complete: () => {
|
|
if (!dataResolved) {
|
|
this.cancelNavigationTransition(t, typeof ngDevMode === 'undefined' || ngDevMode ? `At least one route resolver didn't emit any value.` : '', NavigationCancellationCode.NoDataFromResolver);
|
|
}
|
|
}
|
|
}));
|
|
}), switchTap(t => {
|
|
const loadComponents = route => {
|
|
const loaders = [];
|
|
if (route.routeConfig?._loadedComponent) {
|
|
route.component = route.routeConfig?._loadedComponent;
|
|
} else if (route.routeConfig?.loadComponent) {
|
|
const injector = route._environmentInjector;
|
|
loaders.push(this.configLoader.loadComponent(injector, route.routeConfig).then(loadedComponent => {
|
|
route.component = loadedComponent;
|
|
}));
|
|
}
|
|
for (const child of route.children) {
|
|
loaders.push(...loadComponents(child));
|
|
}
|
|
return loaders;
|
|
};
|
|
const loaders = loadComponents(t.targetSnapshot.root);
|
|
return loaders.length === 0 ? of(t) : from(Promise.all(loaders).then(() => t));
|
|
}), switchTap(() => this.afterPreactivation()), switchMap(() => {
|
|
const {
|
|
currentSnapshot,
|
|
targetSnapshot
|
|
} = overallTransitionState;
|
|
const viewTransitionStarted = this.createViewTransition?.(this.environmentInjector, currentSnapshot.root, targetSnapshot.root);
|
|
return viewTransitionStarted ? from(viewTransitionStarted).pipe(map(() => overallTransitionState)) : of(overallTransitionState);
|
|
}), take(1), map(t => {
|
|
const targetRouterState = createRouterState(router.routeReuseStrategy, t.targetSnapshot, t.currentRouterState);
|
|
this.currentTransition = overallTransitionState = t = {
|
|
...t,
|
|
targetRouterState
|
|
};
|
|
this.currentNavigation.update(nav => {
|
|
nav.targetRouterState = targetRouterState;
|
|
return nav;
|
|
});
|
|
this.events.next(new BeforeActivateRoutes());
|
|
if (!shouldContinueNavigation()) {
|
|
return;
|
|
}
|
|
new ActivateRoutes(router.routeReuseStrategy, overallTransitionState.targetRouterState, overallTransitionState.currentRouterState, evt => this.events.next(evt), this.inputBindingEnabled).activate(this.rootContexts);
|
|
if (!shouldContinueNavigation()) {
|
|
return;
|
|
}
|
|
completedOrAborted = true;
|
|
this.currentNavigation.update(nav => {
|
|
nav.abort = noop;
|
|
return nav;
|
|
});
|
|
this.lastSuccessfulNavigation.set(untracked(this.currentNavigation));
|
|
this.events.next(new NavigationEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects)));
|
|
this.titleStrategy?.updateTitle(t.targetRouterState.snapshot);
|
|
t.resolve(true);
|
|
}), takeUntil(abortSignalToObservable(abortController.signal).pipe(filter(() => !completedOrAborted && !overallTransitionState.targetRouterState), tap(() => {
|
|
this.cancelNavigationTransition(overallTransitionState, abortController.signal.reason + '', NavigationCancellationCode.Aborted);
|
|
}))), tap({
|
|
complete: () => {
|
|
completedOrAborted = true;
|
|
}
|
|
}), takeUntil(this.transitionAbortWithErrorSubject.pipe(tap(err => {
|
|
throw err;
|
|
}))), finalize(() => {
|
|
abortController.abort();
|
|
if (!completedOrAborted) {
|
|
const cancelationReason = typeof ngDevMode === 'undefined' || ngDevMode ? `Navigation ID ${overallTransitionState.id} is not equal to the current navigation id ${this.navigationId}` : '';
|
|
this.cancelNavigationTransition(overallTransitionState, cancelationReason, NavigationCancellationCode.SupersededByNewNavigation);
|
|
}
|
|
if (this.currentTransition?.id === overallTransitionState.id) {
|
|
this.currentNavigation.set(null);
|
|
this.currentTransition = null;
|
|
}
|
|
}), catchError(e => {
|
|
completedOrAborted = true;
|
|
if (this.destroyed) {
|
|
overallTransitionState.resolve(false);
|
|
return EMPTY;
|
|
}
|
|
if (isNavigationCancelingError(e)) {
|
|
this.events.next(new NavigationCancel(overallTransitionState.id, this.urlSerializer.serialize(overallTransitionState.extractedUrl), e.message, e.cancellationCode));
|
|
if (!isRedirectingNavigationCancelingError(e)) {
|
|
overallTransitionState.resolve(false);
|
|
} else {
|
|
this.events.next(new RedirectRequest(e.url, e.navigationBehaviorOptions));
|
|
}
|
|
} else {
|
|
const navigationError = new NavigationError(overallTransitionState.id, this.urlSerializer.serialize(overallTransitionState.extractedUrl), e, overallTransitionState.targetSnapshot ?? undefined);
|
|
try {
|
|
const navigationErrorHandlerResult = runInInjectionContext(this.environmentInjector, () => this.navigationErrorHandler?.(navigationError));
|
|
if (navigationErrorHandlerResult instanceof RedirectCommand) {
|
|
const {
|
|
message,
|
|
cancellationCode
|
|
} = redirectingNavigationError(this.urlSerializer, navigationErrorHandlerResult);
|
|
this.events.next(new NavigationCancel(overallTransitionState.id, this.urlSerializer.serialize(overallTransitionState.extractedUrl), message, cancellationCode));
|
|
this.events.next(new RedirectRequest(navigationErrorHandlerResult.redirectTo, navigationErrorHandlerResult.navigationBehaviorOptions));
|
|
} else {
|
|
this.events.next(navigationError);
|
|
throw e;
|
|
}
|
|
} catch (ee) {
|
|
if (this.options.resolveNavigationPromiseOnError) {
|
|
overallTransitionState.resolve(false);
|
|
} else {
|
|
overallTransitionState.reject(ee);
|
|
}
|
|
}
|
|
}
|
|
return EMPTY;
|
|
}));
|
|
}));
|
|
}
|
|
cancelNavigationTransition(t, reason, code) {
|
|
const navCancel = new NavigationCancel(t.id, this.urlSerializer.serialize(t.extractedUrl), reason, code);
|
|
this.events.next(navCancel);
|
|
t.resolve(false);
|
|
}
|
|
isUpdatingInternalState() {
|
|
return this.currentTransition?.extractedUrl.toString() !== this.currentTransition?.currentUrlTree.toString();
|
|
}
|
|
isUpdatedBrowserUrl() {
|
|
const currentBrowserUrl = this.urlHandlingStrategy.extract(this.urlSerializer.parse(this.location.path(true)));
|
|
const currentNavigation = untracked(this.currentNavigation);
|
|
const targetBrowserUrl = currentNavigation?.targetBrowserUrl ?? currentNavigation?.extractedUrl;
|
|
return currentBrowserUrl.toString() !== targetBrowserUrl?.toString() && !currentNavigation?.extras.skipLocationChange;
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: NavigationTransitions,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: NavigationTransitions,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: NavigationTransitions,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}],
|
|
ctorParameters: () => []
|
|
});
|
|
function isBrowserTriggeredNavigation(source) {
|
|
return source !== IMPERATIVE_NAVIGATION;
|
|
}
|
|
|
|
const ROUTE_INJECTOR_CLEANUP = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'RouteInjectorCleanup' : '');
|
|
function routeInjectorCleanup(routeReuseStrategy, routerState, config) {
|
|
const activeRoutes = new Set();
|
|
if (routerState.snapshot.root) {
|
|
collectDescendants(routerState.snapshot.root, activeRoutes);
|
|
}
|
|
const storedHandles = routeReuseStrategy.retrieveStoredRouteHandles?.() || [];
|
|
for (const handle of storedHandles) {
|
|
const internalHandle = handle;
|
|
if (internalHandle?.route?.value?.snapshot) {
|
|
for (const snapshot of internalHandle.route.value.snapshot.pathFromRoot) {
|
|
if (snapshot.routeConfig) {
|
|
activeRoutes.add(snapshot.routeConfig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
destroyUnusedInjectors(config, activeRoutes, routeReuseStrategy, false);
|
|
}
|
|
function collectDescendants(snapshot, activeRoutes) {
|
|
if (snapshot.routeConfig) {
|
|
activeRoutes.add(snapshot.routeConfig);
|
|
}
|
|
for (const child of snapshot.children) {
|
|
collectDescendants(child, activeRoutes);
|
|
}
|
|
}
|
|
function destroyUnusedInjectors(routes, activeRoutes, strategy, inheritedForceDestroy) {
|
|
for (const route of routes) {
|
|
const shouldDestroyCurrentRoute = inheritedForceDestroy || !!((route._injector || route._loadedInjector) && !activeRoutes.has(route) && (strategy.shouldDestroyInjector?.(route) ?? false));
|
|
if (route.children) {
|
|
destroyUnusedInjectors(route.children, activeRoutes, strategy, shouldDestroyCurrentRoute);
|
|
}
|
|
if (route.loadChildren && route._loadedRoutes) {
|
|
destroyUnusedInjectors(route._loadedRoutes, activeRoutes, strategy, shouldDestroyCurrentRoute);
|
|
}
|
|
if (shouldDestroyCurrentRoute) {
|
|
if (route._injector) {
|
|
route._injector.destroy();
|
|
route._injector = undefined;
|
|
}
|
|
if (route._loadedInjector) {
|
|
route._loadedInjector.destroy();
|
|
route._loadedInjector = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function destroyDetachedRouteHandle(handle) {
|
|
const internalHandle = handle;
|
|
if (internalHandle && internalHandle.componentRef) {
|
|
internalHandle.componentRef.destroy();
|
|
}
|
|
}
|
|
class RouteReuseStrategy {
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouteReuseStrategy,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouteReuseStrategy,
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultRouteReuseStrategy)
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: RouteReuseStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root',
|
|
useFactory: () => inject(DefaultRouteReuseStrategy)
|
|
}]
|
|
}]
|
|
});
|
|
class BaseRouteReuseStrategy {
|
|
shouldDetach(route) {
|
|
return false;
|
|
}
|
|
store(route, detachedTree) {}
|
|
shouldAttach(route) {
|
|
return false;
|
|
}
|
|
retrieve(route) {
|
|
return null;
|
|
}
|
|
shouldReuseRoute(future, curr) {
|
|
return future.routeConfig === curr.routeConfig;
|
|
}
|
|
shouldDestroyInjector(route) {
|
|
return true;
|
|
}
|
|
}
|
|
class DefaultRouteReuseStrategy extends BaseRouteReuseStrategy {
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultRouteReuseStrategy,
|
|
deps: null,
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultRouteReuseStrategy,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: DefaultRouteReuseStrategy,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}]
|
|
});
|
|
|
|
class StateManager {
|
|
urlSerializer = inject(UrlSerializer);
|
|
options = inject(ROUTER_CONFIGURATION, {
|
|
optional: true
|
|
}) || {};
|
|
canceledNavigationResolution = this.options.canceledNavigationResolution || 'replace';
|
|
location = inject(Location);
|
|
urlHandlingStrategy = inject(UrlHandlingStrategy);
|
|
urlUpdateStrategy = this.options.urlUpdateStrategy || 'deferred';
|
|
currentUrlTree = new UrlTree();
|
|
getCurrentUrlTree() {
|
|
return this.currentUrlTree;
|
|
}
|
|
rawUrlTree = this.currentUrlTree;
|
|
getRawUrlTree() {
|
|
return this.rawUrlTree;
|
|
}
|
|
createBrowserPath({
|
|
finalUrl,
|
|
initialUrl,
|
|
targetBrowserUrl
|
|
}) {
|
|
const rawUrl = finalUrl !== undefined ? this.urlHandlingStrategy.merge(finalUrl, initialUrl) : initialUrl;
|
|
const url = targetBrowserUrl ?? rawUrl;
|
|
const path = url instanceof UrlTree ? this.urlSerializer.serialize(url) : url;
|
|
return path;
|
|
}
|
|
commitTransition({
|
|
targetRouterState,
|
|
finalUrl,
|
|
initialUrl
|
|
}) {
|
|
if (finalUrl && targetRouterState) {
|
|
this.currentUrlTree = finalUrl;
|
|
this.rawUrlTree = this.urlHandlingStrategy.merge(finalUrl, initialUrl);
|
|
this.routerState = targetRouterState;
|
|
} else {
|
|
this.rawUrlTree = initialUrl;
|
|
}
|
|
}
|
|
routerState = createEmptyState(null, inject(EnvironmentInjector));
|
|
getRouterState() {
|
|
return this.routerState;
|
|
}
|
|
_stateMemento = this.createStateMemento();
|
|
get stateMemento() {
|
|
return this._stateMemento;
|
|
}
|
|
updateStateMemento() {
|
|
this._stateMemento = this.createStateMemento();
|
|
}
|
|
createStateMemento() {
|
|
return {
|
|
rawUrlTree: this.rawUrlTree,
|
|
currentUrlTree: this.currentUrlTree,
|
|
routerState: this.routerState
|
|
};
|
|
}
|
|
restoredState() {
|
|
return this.location.getState();
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: StateManager,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: StateManager,
|
|
providedIn: 'root',
|
|
useFactory: () => inject(HistoryStateManager)
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: StateManager,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root',
|
|
useFactory: () => inject(HistoryStateManager)
|
|
}]
|
|
}]
|
|
});
|
|
class HistoryStateManager extends StateManager {
|
|
currentPageId = 0;
|
|
lastSuccessfulId = -1;
|
|
get browserPageId() {
|
|
if (this.canceledNavigationResolution !== 'computed') {
|
|
return this.currentPageId;
|
|
}
|
|
return this.restoredState()?.ɵrouterPageId ?? this.currentPageId;
|
|
}
|
|
registerNonRouterCurrentEntryChangeListener(listener) {
|
|
return this.location.subscribe(event => {
|
|
if (event['type'] === 'popstate') {
|
|
setTimeout(() => {
|
|
listener(event['url'], event.state, 'popstate');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
handleRouterEvent(e, currentTransition) {
|
|
if (e instanceof NavigationStart) {
|
|
this.updateStateMemento();
|
|
} else if (e instanceof NavigationSkipped) {
|
|
this.commitTransition(currentTransition);
|
|
} else if (e instanceof RoutesRecognized) {
|
|
if (this.urlUpdateStrategy === 'eager') {
|
|
if (!currentTransition.extras.skipLocationChange) {
|
|
this.setBrowserUrl(this.createBrowserPath(currentTransition), currentTransition);
|
|
}
|
|
}
|
|
} else if (e instanceof BeforeActivateRoutes) {
|
|
this.commitTransition(currentTransition);
|
|
if (this.urlUpdateStrategy === 'deferred' && !currentTransition.extras.skipLocationChange) {
|
|
this.setBrowserUrl(this.createBrowserPath(currentTransition), currentTransition);
|
|
}
|
|
} else if (e instanceof NavigationCancel && !isRedirectingEvent(e)) {
|
|
this.restoreHistory(currentTransition);
|
|
} else if (e instanceof NavigationError) {
|
|
this.restoreHistory(currentTransition, true);
|
|
} else if (e instanceof NavigationEnd) {
|
|
this.lastSuccessfulId = e.id;
|
|
this.currentPageId = this.browserPageId;
|
|
}
|
|
}
|
|
setBrowserUrl(path, {
|
|
extras,
|
|
id
|
|
}) {
|
|
const {
|
|
replaceUrl,
|
|
state
|
|
} = extras;
|
|
if (this.location.isCurrentPathEqualTo(path) || !!replaceUrl) {
|
|
const currentBrowserPageId = this.browserPageId;
|
|
const newState = {
|
|
...state,
|
|
...this.generateNgRouterState(id, currentBrowserPageId)
|
|
};
|
|
this.location.replaceState(path, '', newState);
|
|
} else {
|
|
const newState = {
|
|
...state,
|
|
...this.generateNgRouterState(id, this.browserPageId + 1)
|
|
};
|
|
this.location.go(path, '', newState);
|
|
}
|
|
}
|
|
restoreHistory(navigation, restoringFromCaughtError = false) {
|
|
if (this.canceledNavigationResolution === 'computed') {
|
|
const currentBrowserPageId = this.browserPageId;
|
|
const targetPagePosition = this.currentPageId - currentBrowserPageId;
|
|
if (targetPagePosition !== 0) {
|
|
this.location.historyGo(targetPagePosition);
|
|
} else if (this.getCurrentUrlTree() === navigation.finalUrl && targetPagePosition === 0) {
|
|
this.resetInternalState(navigation);
|
|
this.resetUrlToCurrentUrlTree();
|
|
} else ;
|
|
} else if (this.canceledNavigationResolution === 'replace') {
|
|
if (restoringFromCaughtError) {
|
|
this.resetInternalState(navigation);
|
|
}
|
|
this.resetUrlToCurrentUrlTree();
|
|
}
|
|
}
|
|
resetInternalState({
|
|
finalUrl
|
|
}) {
|
|
this.routerState = this.stateMemento.routerState;
|
|
this.currentUrlTree = this.stateMemento.currentUrlTree;
|
|
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, finalUrl ?? this.rawUrlTree);
|
|
}
|
|
resetUrlToCurrentUrlTree() {
|
|
this.location.replaceState(this.urlSerializer.serialize(this.getRawUrlTree()), '', this.generateNgRouterState(this.lastSuccessfulId, this.currentPageId));
|
|
}
|
|
generateNgRouterState(navigationId, routerPageId) {
|
|
if (this.canceledNavigationResolution === 'computed') {
|
|
return {
|
|
navigationId,
|
|
ɵrouterPageId: routerPageId
|
|
};
|
|
}
|
|
return {
|
|
navigationId
|
|
};
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: HistoryStateManager,
|
|
deps: null,
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: HistoryStateManager,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: HistoryStateManager,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}]
|
|
});
|
|
|
|
function afterNextNavigation(router, action) {
|
|
router.events.pipe(filter(e => e instanceof NavigationEnd || e instanceof NavigationCancel || e instanceof NavigationError || e instanceof NavigationSkipped), map(e => {
|
|
if (e instanceof NavigationEnd || e instanceof NavigationSkipped) {
|
|
return 0;
|
|
}
|
|
const redirecting = e instanceof NavigationCancel ? e.code === NavigationCancellationCode.Redirect || e.code === NavigationCancellationCode.SupersededByNewNavigation : false;
|
|
return redirecting ? 2 : 1;
|
|
}), filter(result => result !== 2), take(1)).subscribe(() => {
|
|
action();
|
|
});
|
|
}
|
|
|
|
const exactMatchOptions = {
|
|
paths: 'exact',
|
|
fragment: 'ignored',
|
|
matrixParams: 'ignored',
|
|
queryParams: 'exact'
|
|
};
|
|
const subsetMatchOptions = {
|
|
paths: 'subset',
|
|
fragment: 'ignored',
|
|
matrixParams: 'ignored',
|
|
queryParams: 'subset'
|
|
};
|
|
class Router {
|
|
get currentUrlTree() {
|
|
return this.stateManager.getCurrentUrlTree();
|
|
}
|
|
get rawUrlTree() {
|
|
return this.stateManager.getRawUrlTree();
|
|
}
|
|
disposed = false;
|
|
nonRouterCurrentEntryChangeSubscription;
|
|
console = inject(_Console);
|
|
stateManager = inject(StateManager);
|
|
options = inject(ROUTER_CONFIGURATION, {
|
|
optional: true
|
|
}) || {};
|
|
pendingTasks = inject(_PendingTasksInternal);
|
|
urlUpdateStrategy = this.options.urlUpdateStrategy || 'deferred';
|
|
navigationTransitions = inject(NavigationTransitions);
|
|
urlSerializer = inject(UrlSerializer);
|
|
location = inject(Location);
|
|
urlHandlingStrategy = inject(UrlHandlingStrategy);
|
|
injector = inject(EnvironmentInjector);
|
|
_events = new Subject();
|
|
get events() {
|
|
return this._events;
|
|
}
|
|
get routerState() {
|
|
return this.stateManager.getRouterState();
|
|
}
|
|
navigated = false;
|
|
routeReuseStrategy = inject(RouteReuseStrategy);
|
|
injectorCleanup = inject(ROUTE_INJECTOR_CLEANUP, {
|
|
optional: true
|
|
});
|
|
onSameUrlNavigation = this.options.onSameUrlNavigation || 'ignore';
|
|
config = inject(ROUTES, {
|
|
optional: true
|
|
})?.flat() ?? [];
|
|
componentInputBindingEnabled = !!inject(INPUT_BINDER, {
|
|
optional: true
|
|
});
|
|
currentNavigation = this.navigationTransitions.currentNavigation.asReadonly();
|
|
constructor() {
|
|
this.resetConfig(this.config);
|
|
this.navigationTransitions.setupNavigations(this).subscribe({
|
|
error: e => {}
|
|
});
|
|
this.subscribeToNavigationEvents();
|
|
}
|
|
eventsSubscription = new Subscription();
|
|
subscribeToNavigationEvents() {
|
|
const subscription = this.navigationTransitions.events.subscribe(e => {
|
|
try {
|
|
const currentTransition = this.navigationTransitions.currentTransition;
|
|
const currentNavigation = untracked(this.navigationTransitions.currentNavigation);
|
|
if (currentTransition !== null && currentNavigation !== null) {
|
|
this.stateManager.handleRouterEvent(e, currentNavigation);
|
|
if (e instanceof NavigationCancel && e.code !== NavigationCancellationCode.Redirect && e.code !== NavigationCancellationCode.SupersededByNewNavigation) {
|
|
this.navigated = true;
|
|
} else if (e instanceof NavigationEnd) {
|
|
this.navigated = true;
|
|
this.injectorCleanup?.(this.routeReuseStrategy, this.routerState, this.config);
|
|
} else if (e instanceof RedirectRequest) {
|
|
const opts = e.navigationBehaviorOptions;
|
|
const mergedTree = this.urlHandlingStrategy.merge(e.url, currentTransition.currentRawUrl);
|
|
const extras = {
|
|
scroll: currentTransition.extras.scroll,
|
|
browserUrl: currentTransition.extras.browserUrl,
|
|
info: currentTransition.extras.info,
|
|
skipLocationChange: currentTransition.extras.skipLocationChange,
|
|
replaceUrl: currentTransition.extras.replaceUrl || this.urlUpdateStrategy === 'eager' || isBrowserTriggeredNavigation(currentTransition.source),
|
|
...opts
|
|
};
|
|
this.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras, {
|
|
resolve: currentTransition.resolve,
|
|
reject: currentTransition.reject,
|
|
promise: currentTransition.promise
|
|
});
|
|
}
|
|
}
|
|
if (isPublicRouterEvent(e)) {
|
|
this._events.next(e);
|
|
}
|
|
} catch (e) {
|
|
this.navigationTransitions.transitionAbortWithErrorSubject.next(e);
|
|
}
|
|
});
|
|
this.eventsSubscription.add(subscription);
|
|
}
|
|
resetRootComponentType(rootComponentType) {
|
|
this.routerState.root.component = rootComponentType;
|
|
this.navigationTransitions.rootComponentType = rootComponentType;
|
|
}
|
|
initialNavigation() {
|
|
this.setUpLocationChangeListener();
|
|
if (!this.navigationTransitions.hasRequestedNavigation) {
|
|
this.navigateToSyncWithBrowser(this.location.path(true), IMPERATIVE_NAVIGATION, this.stateManager.restoredState());
|
|
}
|
|
}
|
|
setUpLocationChangeListener() {
|
|
this.nonRouterCurrentEntryChangeSubscription ??= this.stateManager.registerNonRouterCurrentEntryChangeListener((url, state, source) => {
|
|
this.navigateToSyncWithBrowser(url, source, state);
|
|
});
|
|
}
|
|
navigateToSyncWithBrowser(url, source, state) {
|
|
const extras = {
|
|
replaceUrl: true
|
|
};
|
|
const restoredState = state?.navigationId ? state : null;
|
|
if (state) {
|
|
const stateCopy = {
|
|
...state
|
|
};
|
|
delete stateCopy.navigationId;
|
|
delete stateCopy.ɵrouterPageId;
|
|
if (Object.keys(stateCopy).length !== 0) {
|
|
extras.state = stateCopy;
|
|
}
|
|
}
|
|
const urlTree = this.parseUrl(url);
|
|
this.scheduleNavigation(urlTree, source, restoredState, extras).catch(e => {
|
|
if (this.disposed) {
|
|
return;
|
|
}
|
|
this.injector.get(_INTERNAL_APPLICATION_ERROR_HANDLER)(e);
|
|
});
|
|
}
|
|
get url() {
|
|
return this.serializeUrl(this.currentUrlTree);
|
|
}
|
|
getCurrentNavigation() {
|
|
return untracked(this.navigationTransitions.currentNavigation);
|
|
}
|
|
get lastSuccessfulNavigation() {
|
|
return this.navigationTransitions.lastSuccessfulNavigation;
|
|
}
|
|
resetConfig(config) {
|
|
(typeof ngDevMode === 'undefined' || ngDevMode) && validateConfig(config);
|
|
this.config = config.map(standardizeConfig);
|
|
this.navigated = false;
|
|
}
|
|
ngOnDestroy() {
|
|
this.dispose();
|
|
}
|
|
dispose() {
|
|
this._events.unsubscribe();
|
|
this.navigationTransitions.complete();
|
|
this.nonRouterCurrentEntryChangeSubscription?.unsubscribe();
|
|
this.nonRouterCurrentEntryChangeSubscription = undefined;
|
|
this.disposed = true;
|
|
this.eventsSubscription.unsubscribe();
|
|
}
|
|
createUrlTree(commands, navigationExtras = {}) {
|
|
const {
|
|
relativeTo,
|
|
queryParams,
|
|
fragment,
|
|
queryParamsHandling,
|
|
preserveFragment
|
|
} = navigationExtras;
|
|
const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
|
|
let q = null;
|
|
switch (queryParamsHandling ?? this.options.defaultQueryParamsHandling) {
|
|
case 'merge':
|
|
q = {
|
|
...this.currentUrlTree.queryParams,
|
|
...queryParams
|
|
};
|
|
break;
|
|
case 'preserve':
|
|
q = this.currentUrlTree.queryParams;
|
|
break;
|
|
default:
|
|
q = queryParams || null;
|
|
}
|
|
if (q !== null) {
|
|
q = this.removeEmptyProps(q);
|
|
}
|
|
let relativeToUrlSegmentGroup;
|
|
try {
|
|
const relativeToSnapshot = relativeTo ? relativeTo.snapshot : this.routerState.snapshot.root;
|
|
relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeToSnapshot);
|
|
} catch (e) {
|
|
if (typeof commands[0] !== 'string' || commands[0][0] !== '/') {
|
|
commands = [];
|
|
}
|
|
relativeToUrlSegmentGroup = this.currentUrlTree.root;
|
|
}
|
|
return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, q, f ?? null, this.urlSerializer);
|
|
}
|
|
navigateByUrl(url, extras = {
|
|
skipLocationChange: false
|
|
}) {
|
|
const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
|
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
|
return this.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras);
|
|
}
|
|
navigate(commands, extras = {
|
|
skipLocationChange: false
|
|
}) {
|
|
validateCommands(commands);
|
|
return this.navigateByUrl(this.createUrlTree(commands, extras), extras);
|
|
}
|
|
serializeUrl(url) {
|
|
return this.urlSerializer.serialize(url);
|
|
}
|
|
parseUrl(url) {
|
|
try {
|
|
return this.urlSerializer.parse(url);
|
|
} catch (e) {
|
|
this.console.warn(_formatRuntimeError(4018, ngDevMode && `Error parsing URL ${url}. Falling back to '/' instead. \n` + e));
|
|
return this.urlSerializer.parse('/');
|
|
}
|
|
}
|
|
isActive(url, matchOptions) {
|
|
let options;
|
|
if (matchOptions === true) {
|
|
options = {
|
|
...exactMatchOptions
|
|
};
|
|
} else if (matchOptions === false) {
|
|
options = {
|
|
...subsetMatchOptions
|
|
};
|
|
} else {
|
|
options = matchOptions;
|
|
}
|
|
if (isUrlTree(url)) {
|
|
return containsTree(this.currentUrlTree, url, options);
|
|
}
|
|
const urlTree = this.parseUrl(url);
|
|
return containsTree(this.currentUrlTree, urlTree, options);
|
|
}
|
|
removeEmptyProps(params) {
|
|
return Object.entries(params).reduce((result, [key, value]) => {
|
|
if (value !== null && value !== undefined) {
|
|
result[key] = value;
|
|
}
|
|
return result;
|
|
}, {});
|
|
}
|
|
scheduleNavigation(rawUrl, source, restoredState, extras, priorPromise) {
|
|
if (this.disposed) {
|
|
return Promise.resolve(false);
|
|
}
|
|
let resolve;
|
|
let reject;
|
|
let promise;
|
|
if (priorPromise) {
|
|
resolve = priorPromise.resolve;
|
|
reject = priorPromise.reject;
|
|
promise = priorPromise.promise;
|
|
} else {
|
|
promise = new Promise((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
}
|
|
const taskId = this.pendingTasks.add();
|
|
afterNextNavigation(this, () => {
|
|
queueMicrotask(() => this.pendingTasks.remove(taskId));
|
|
});
|
|
this.navigationTransitions.handleNavigationRequest({
|
|
source,
|
|
restoredState,
|
|
currentUrlTree: this.currentUrlTree,
|
|
currentRawUrl: this.currentUrlTree,
|
|
rawUrl,
|
|
extras,
|
|
resolve: resolve,
|
|
reject: reject,
|
|
promise,
|
|
currentSnapshot: this.routerState.snapshot,
|
|
currentRouterState: this.routerState
|
|
});
|
|
return promise.catch(e => {
|
|
return Promise.reject(e);
|
|
});
|
|
}
|
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: Router,
|
|
deps: [],
|
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
});
|
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: Router,
|
|
providedIn: 'root'
|
|
});
|
|
}
|
|
i0.ɵɵngDeclareClassMetadata({
|
|
minVersion: "12.0.0",
|
|
version: "21.1.1",
|
|
ngImport: i0,
|
|
type: Router,
|
|
decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'root'
|
|
}]
|
|
}],
|
|
ctorParameters: () => []
|
|
});
|
|
function validateCommands(commands) {
|
|
for (let i = 0; i < commands.length; i++) {
|
|
const cmd = commands[i];
|
|
if (cmd == null) {
|
|
throw new _RuntimeError(4008, (typeof ngDevMode === 'undefined' || ngDevMode) && `The requested path contains ${cmd} segment at index ${i}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export { ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, BaseRouteReuseStrategy, BeforeActivateRoutes, CREATE_VIEW_TRANSITION, ChildActivationEnd, ChildActivationStart, ChildrenOutletContexts, DefaultTitleStrategy, DefaultUrlSerializer, EventType, GuardsCheckEnd, GuardsCheckStart, IMPERATIVE_NAVIGATION, INPUT_BINDER, NAVIGATION_ERROR_HANDLER, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationSkippedCode, NavigationStart, NavigationTransitions, OutletContext, PRIMARY_OUTLET, ROUTER_CONFIGURATION, ROUTER_OUTLET_DATA, ROUTES, ROUTE_INJECTOR_CLEANUP, RedirectCommand, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, RoutedComponentInputBinder, Router, RouterConfigLoader, RouterEvent, RouterOutlet, RouterState, RouterStateSnapshot, RoutesRecognized, Scroll, StateManager, TitleStrategy, UrlHandlingStrategy, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree, VIEW_TRANSITION_OPTIONS, afterNextNavigation, convertToParamMap, createUrlTreeFromSnapshot, createViewTransition, defaultUrlMatcher, destroyDetachedRouteHandle, exactMatchOptions, isActive, isRedirectingEvent, isUrlTree, loadChildren, routeInjectorCleanup, stringifyEvent, subsetMatchOptions, ɵEmptyOutletComponent };
|
|
//# sourceMappingURL=_router-chunk.mjs.map
|
|
|