@angular/router 11.0.4 → 11.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  /**
2
- * @license Angular v11.0.4
2
+ * @license Angular v11.0.8
3
3
  * (c) 2010-2020 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
6
6
 
7
7
  import { Location, LocationStrategy, ViewportScroller, PlatformLocation, APP_BASE_HREF, HashLocationStrategy, PathLocationStrategy, ɵgetDOM, LOCATION_INITIALIZED } from '@angular/common';
8
8
  import { ɵisObservable, ɵisPromise, Component, NgModuleRef, InjectionToken, NgModuleFactory, ɵConsole, NgZone, Injectable, Type, Injector, NgModuleFactoryLoader, Compiler, Directive, Attribute, Renderer2, ElementRef, Input, HostListener, HostBinding, ChangeDetectorRef, Optional, ContentChildren, EventEmitter, ViewContainerRef, ComponentFactoryResolver, Output, SystemJsNgModuleLoader, NgProbeToken, ANALYZE_FOR_ENTRY_COMPONENTS, SkipSelf, Inject, APP_INITIALIZER, APP_BOOTSTRAP_LISTENER, NgModule, ApplicationRef, Version } from '@angular/core';
9
- import { of, from, BehaviorSubject, combineLatest, Observable, EmptyError, defer, EMPTY, Subject } from 'rxjs';
10
- import { map, concatAll, last as last$1, switchMap, take, startWith, scan, filter, catchError, concatMap, first, combineAll, mergeMap, tap, takeLast, finalize, mergeAll } from 'rxjs/operators';
9
+ import { from, of, BehaviorSubject, combineLatest, Observable, EmptyError, defer, EMPTY, Subject } from 'rxjs';
10
+ import { map, switchMap, take, startWith, scan, filter, catchError, concatMap, last as last$1, first, mergeMap, tap, concatAll, takeLast, finalize, mergeAll } from 'rxjs/operators';
11
11
 
12
12
  /**
13
13
  * @license
@@ -571,28 +571,6 @@ function forEach(map, callback) {
571
571
  }
572
572
  }
573
573
  }
574
- function waitForMap(obj, fn) {
575
- if (Object.keys(obj).length === 0) {
576
- return of({});
577
- }
578
- const waitHead = [];
579
- const waitTail = [];
580
- const res = {};
581
- forEach(obj, (a, k) => {
582
- const mapped = fn(k, a).pipe(map((r) => res[k] = r));
583
- if (k === PRIMARY_OUTLET) {
584
- waitHead.push(mapped);
585
- }
586
- else {
587
- waitTail.push(mapped);
588
- }
589
- });
590
- // Closure compiler has problem with using spread operator here. So we use "Array.concat".
591
- // Note that we also need to cast the new promise because TypeScript cannot infer the type
592
- // when calling the "of" function through "Function.apply"
593
- return of.apply(null, waitHead.concat(waitTail))
594
- .pipe(concatAll(), last$1(), map(() => res));
595
- }
596
574
  function wrapIntoObservable(value) {
597
575
  if (ɵisObservable(value)) {
598
576
  return value;
@@ -1480,7 +1458,25 @@ class ActivatedRouteSnapshot {
1480
1458
  constructor(
1481
1459
  /** The URL segments matched by this route */
1482
1460
  url,
1483
- /** The matrix parameters scoped to this route */
1461
+ /**
1462
+ * The matrix parameters scoped to this route.
1463
+ *
1464
+ * You can compute all params (or data) in the router state or to get params outside
1465
+ * of an activated component by traversing the `RouterState` tree as in the following
1466
+ * example:
1467
+ * ```
1468
+ * collectRouteParams(router: Router) {
1469
+ * let params = {};
1470
+ * let stack: ActivatedRouteSnapshot[] = [router.routerState.snapshot.root];
1471
+ * while (stack.length > 0) {
1472
+ * const route = stack.pop()!;
1473
+ * params = {...params, ...route.params};
1474
+ * stack.push(...route.children);
1475
+ * }
1476
+ * return params;
1477
+ * }
1478
+ * ```
1479
+ */
1484
1480
  params,
1485
1481
  /** The query parameters shared by all the routes */
1486
1482
  queryParams,
@@ -2064,16 +2060,18 @@ class ActivateRoutes {
2064
2060
  }
2065
2061
  deactivateRouteAndOutlet(route, parentContexts) {
2066
2062
  const context = parentContexts.getContext(route.value.outlet);
2067
- if (context) {
2068
- const children = nodeChildrenAsMap(route);
2069
- const contexts = route.value.component ? context.children : parentContexts;
2070
- forEach(children, (v, k) => this.deactivateRouteAndItsChildren(v, contexts));
2071
- if (context.outlet) {
2072
- // Destroy the component
2073
- context.outlet.deactivate();
2074
- // Destroy the contexts for all the outlets that were in the component
2075
- context.children.onOutletDeactivated();
2076
- }
2063
+ // The context could be `null` if we are on a componentless route but there may still be
2064
+ // children that need deactivating.
2065
+ const contexts = context && route.value.component ? context.children : parentContexts;
2066
+ const children = nodeChildrenAsMap(route);
2067
+ for (const childOutlet of Object.keys(children)) {
2068
+ this.deactivateRouteAndItsChildren(children[childOutlet], contexts);
2069
+ }
2070
+ if (context && context.outlet) {
2071
+ // Destroy the component
2072
+ context.outlet.deactivate();
2073
+ // Destroy the contexts for all the outlets that were in the component
2074
+ context.children.onOutletDeactivated();
2077
2075
  }
2078
2076
  }
2079
2077
  activateChildRoutes(futureNode, currNode, contexts) {
@@ -2373,23 +2371,160 @@ function standardizeConfig(r) {
2373
2371
  }
2374
2372
  return c;
2375
2373
  }
2376
- /** Returns of `Map` of outlet names to the `Route`s for that outlet. */
2377
- function groupRoutesByOutlet(routes) {
2378
- return routes.reduce((map, route) => {
2379
- const routeOutlet = getOutlet(route);
2380
- if (map.has(routeOutlet)) {
2381
- map.get(routeOutlet).push(route);
2382
- }
2383
- else {
2384
- map.set(routeOutlet, [route]);
2385
- }
2386
- return map;
2387
- }, new Map());
2388
- }
2389
2374
  /** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */
2390
2375
  function getOutlet(route) {
2391
2376
  return route.outlet || PRIMARY_OUTLET;
2392
2377
  }
2378
+ /**
2379
+ * Sorts the `routes` such that the ones with an outlet matching `outletName` come first.
2380
+ * The order of the configs is otherwise preserved.
2381
+ */
2382
+ function sortByMatchingOutlets(routes, outletName) {
2383
+ const sortedConfig = routes.filter(r => getOutlet(r) === outletName);
2384
+ sortedConfig.push(...routes.filter(r => getOutlet(r) !== outletName));
2385
+ return sortedConfig;
2386
+ }
2387
+
2388
+ /**
2389
+ * @license
2390
+ * Copyright Google LLC All Rights Reserved.
2391
+ *
2392
+ * Use of this source code is governed by an MIT-style license that can be
2393
+ * found in the LICENSE file at https://angular.io/license
2394
+ */
2395
+ const noMatch = {
2396
+ matched: false,
2397
+ consumedSegments: [],
2398
+ lastChild: 0,
2399
+ parameters: {},
2400
+ positionalParamSegments: {}
2401
+ };
2402
+ function match(segmentGroup, route, segments) {
2403
+ var _a;
2404
+ if (route.path === '') {
2405
+ if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
2406
+ return Object.assign({}, noMatch);
2407
+ }
2408
+ return {
2409
+ matched: true,
2410
+ consumedSegments: [],
2411
+ lastChild: 0,
2412
+ parameters: {},
2413
+ positionalParamSegments: {}
2414
+ };
2415
+ }
2416
+ const matcher = route.matcher || defaultUrlMatcher;
2417
+ const res = matcher(segments, segmentGroup, route);
2418
+ if (!res)
2419
+ return Object.assign({}, noMatch);
2420
+ const posParams = {};
2421
+ forEach(res.posParams, (v, k) => {
2422
+ posParams[k] = v.path;
2423
+ });
2424
+ const parameters = res.consumed.length > 0 ? Object.assign(Object.assign({}, posParams), res.consumed[res.consumed.length - 1].parameters) :
2425
+ posParams;
2426
+ return {
2427
+ matched: true,
2428
+ consumedSegments: res.consumed,
2429
+ lastChild: res.consumed.length,
2430
+ // TODO(atscott): investigate combining parameters and positionalParamSegments
2431
+ parameters,
2432
+ positionalParamSegments: (_a = res.posParams) !== null && _a !== void 0 ? _a : {}
2433
+ };
2434
+ }
2435
+ function split(segmentGroup, consumedSegments, slicedSegments, config, relativeLinkResolution = 'corrected') {
2436
+ if (slicedSegments.length > 0 &&
2437
+ containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) {
2438
+ const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptyPaths(segmentGroup, consumedSegments, config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
2439
+ s._sourceSegment = segmentGroup;
2440
+ s._segmentIndexShift = consumedSegments.length;
2441
+ return { segmentGroup: s, slicedSegments: [] };
2442
+ }
2443
+ if (slicedSegments.length === 0 &&
2444
+ containsEmptyPathMatches(segmentGroup, slicedSegments, config)) {
2445
+ const s = new UrlSegmentGroup(segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, config, segmentGroup.children, relativeLinkResolution));
2446
+ s._sourceSegment = segmentGroup;
2447
+ s._segmentIndexShift = consumedSegments.length;
2448
+ return { segmentGroup: s, slicedSegments };
2449
+ }
2450
+ const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children);
2451
+ s._sourceSegment = segmentGroup;
2452
+ s._segmentIndexShift = consumedSegments.length;
2453
+ return { segmentGroup: s, slicedSegments };
2454
+ }
2455
+ function addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, routes, children, relativeLinkResolution) {
2456
+ const res = {};
2457
+ for (const r of routes) {
2458
+ if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
2459
+ const s = new UrlSegmentGroup([], {});
2460
+ s._sourceSegment = segmentGroup;
2461
+ if (relativeLinkResolution === 'legacy') {
2462
+ s._segmentIndexShift = segmentGroup.segments.length;
2463
+ }
2464
+ else {
2465
+ s._segmentIndexShift = consumedSegments.length;
2466
+ }
2467
+ res[getOutlet(r)] = s;
2468
+ }
2469
+ }
2470
+ return Object.assign(Object.assign({}, children), res);
2471
+ }
2472
+ function createChildrenForEmptyPaths(segmentGroup, consumedSegments, routes, primarySegment) {
2473
+ const res = {};
2474
+ res[PRIMARY_OUTLET] = primarySegment;
2475
+ primarySegment._sourceSegment = segmentGroup;
2476
+ primarySegment._segmentIndexShift = consumedSegments.length;
2477
+ for (const r of routes) {
2478
+ if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) {
2479
+ const s = new UrlSegmentGroup([], {});
2480
+ s._sourceSegment = segmentGroup;
2481
+ s._segmentIndexShift = consumedSegments.length;
2482
+ res[getOutlet(r)] = s;
2483
+ }
2484
+ }
2485
+ return res;
2486
+ }
2487
+ function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) {
2488
+ return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET);
2489
+ }
2490
+ function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) {
2491
+ return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r));
2492
+ }
2493
+ function emptyPathMatch(segmentGroup, slicedSegments, r) {
2494
+ if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') {
2495
+ return false;
2496
+ }
2497
+ return r.path === '';
2498
+ }
2499
+ /**
2500
+ * Determines if `route` is a path match for the `rawSegment`, `segments`, and `outlet` without
2501
+ * verifying that its children are a full match for the remainder of the `rawSegment` children as
2502
+ * well.
2503
+ */
2504
+ function isImmediateMatch(route, rawSegment, segments, outlet) {
2505
+ // We allow matches to empty paths when the outlets differ so we can match a url like `/(b:b)` to
2506
+ // a config like
2507
+ // * `{path: '', children: [{path: 'b', outlet: 'b'}]}`
2508
+ // or even
2509
+ // * `{path: '', outlet: 'a', children: [{path: 'b', outlet: 'b'}]`
2510
+ //
2511
+ // The exception here is when the segment outlet is for the primary outlet. This would
2512
+ // result in a match inside the named outlet because all children there are written as primary
2513
+ // outlets. So we need to prevent child named outlet matches in a url like `/b` in a config like
2514
+ // * `{path: '', outlet: 'x' children: [{path: 'b'}]}`
2515
+ // This should only match if the url is `/(x:b)`.
2516
+ if (getOutlet(route) !== outlet &&
2517
+ (outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))) {
2518
+ return false;
2519
+ }
2520
+ if (route.path === '**') {
2521
+ return true;
2522
+ }
2523
+ return match(rawSegment, route, segments).matched;
2524
+ }
2525
+ function noLeftoversInUrl(segmentGroup, segments, outlet) {
2526
+ return segments.length === 0 && !segmentGroup.children[outlet];
2527
+ }
2393
2528
 
2394
2529
  /**
2395
2530
  * @license
@@ -2408,7 +2543,7 @@ class AbsoluteRedirect {
2408
2543
  this.urlTree = urlTree;
2409
2544
  }
2410
2545
  }
2411
- function noMatch(segmentGroup) {
2546
+ function noMatch$1(segmentGroup) {
2412
2547
  return new Observable((obs) => obs.error(new NoMatch(segmentGroup)));
2413
2548
  }
2414
2549
  function absoluteRedirect(newTree) {
@@ -2438,8 +2573,18 @@ class ApplyRedirects {
2438
2573
  this.ngModule = moduleInjector.get(NgModuleRef);
2439
2574
  }
2440
2575
  apply() {
2441
- const expanded$ = this.expandSegmentGroup(this.ngModule, this.config, this.urlTree.root, PRIMARY_OUTLET);
2442
- const urlTrees$ = expanded$.pipe(map((rootSegmentGroup) => this.createUrlTree(rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment)));
2576
+ const splitGroup = split(this.urlTree.root, [], [], this.config).segmentGroup;
2577
+ // TODO(atscott): creating a new segment removes the _sourceSegment _segmentIndexShift, which is
2578
+ // only necessary to prevent failures in tests which assert exact object matches. The `split` is
2579
+ // now shared between `applyRedirects` and `recognize` but only the `recognize` step needs these
2580
+ // properties. Before the implementations were merged, the `applyRedirects` would not assign
2581
+ // them. We should be able to remove this logic as a "breaking change" but should do some more
2582
+ // investigation into the failures first.
2583
+ const rootSegmentGroup = new UrlSegmentGroup(splitGroup.segments, splitGroup.children);
2584
+ const expanded$ = this.expandSegmentGroup(this.ngModule, this.config, rootSegmentGroup, PRIMARY_OUTLET);
2585
+ const urlTrees$ = expanded$.pipe(map((rootSegmentGroup) => {
2586
+ return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), this.urlTree.queryParams, this.urlTree.fragment);
2587
+ }));
2443
2588
  return urlTrees$.pipe(catchError((e) => {
2444
2589
  if (e instanceof AbsoluteRedirect) {
2445
2590
  // after an absolute redirect we do not apply any more redirects!
@@ -2455,7 +2600,9 @@ class ApplyRedirects {
2455
2600
  }
2456
2601
  match(tree) {
2457
2602
  const expanded$ = this.expandSegmentGroup(this.ngModule, this.config, tree.root, PRIMARY_OUTLET);
2458
- const mapped$ = expanded$.pipe(map((rootSegmentGroup) => this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment)));
2603
+ const mapped$ = expanded$.pipe(map((rootSegmentGroup) => {
2604
+ return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), tree.queryParams, tree.fragment);
2605
+ }));
2459
2606
  return mapped$.pipe(catchError((e) => {
2460
2607
  if (e instanceof NoMatch) {
2461
2608
  throw this.noMatchError(e);
@@ -2481,62 +2628,61 @@ class ApplyRedirects {
2481
2628
  }
2482
2629
  // Recursively expand segment groups for all the child outlets
2483
2630
  expandChildren(ngModule, routes, segmentGroup) {
2484
- return waitForMap(segmentGroup.children, (childOutlet, child) => this.expandSegmentGroup(ngModule, routes, child, childOutlet));
2631
+ // Expand outlets one at a time, starting with the primary outlet. We need to do it this way
2632
+ // because an absolute redirect from the primary outlet takes precedence.
2633
+ const childOutlets = [];
2634
+ for (const child of Object.keys(segmentGroup.children)) {
2635
+ if (child === 'primary') {
2636
+ childOutlets.unshift(child);
2637
+ }
2638
+ else {
2639
+ childOutlets.push(child);
2640
+ }
2641
+ }
2642
+ return from(childOutlets)
2643
+ .pipe(concatMap(childOutlet => {
2644
+ const child = segmentGroup.children[childOutlet];
2645
+ // Sort the routes so routes with outlets that match the the segment appear
2646
+ // first, followed by routes for other outlets, which might match if they have an
2647
+ // empty path.
2648
+ const sortedRoutes = sortByMatchingOutlets(routes, childOutlet);
2649
+ return this.expandSegmentGroup(ngModule, sortedRoutes, child, childOutlet)
2650
+ .pipe(map(s => ({ segment: s, outlet: childOutlet })));
2651
+ }), scan((children, expandedChild) => {
2652
+ children[expandedChild.outlet] = expandedChild.segment;
2653
+ return children;
2654
+ }, {}), last$1());
2485
2655
  }
2486
2656
  expandSegment(ngModule, segmentGroup, routes, segments, outlet, allowRedirects) {
2487
- // We need to expand each outlet group independently to ensure that we not only load modules
2488
- // for routes matching the given `outlet`, but also those which will be activated because
2489
- // their path is empty string. This can result in multiple outlets being activated at once.
2490
- const routesByOutlet = groupRoutesByOutlet(routes);
2491
- if (!routesByOutlet.has(outlet)) {
2492
- routesByOutlet.set(outlet, []);
2493
- }
2494
- const expandRoutes = (routes) => {
2495
- return from(routes).pipe(concatMap((r) => {
2496
- const expanded$ = this.expandSegmentAgainstRoute(ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
2497
- return expanded$.pipe(catchError(e => {
2498
- if (e instanceof NoMatch) {
2499
- return of(null);
2500
- }
2501
- throw e;
2502
- }));
2503
- }), first((s) => s !== null), catchError(e => {
2504
- if (e instanceof EmptyError || e.name === 'EmptyError') {
2505
- if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
2506
- return of(new UrlSegmentGroup([], {}));
2507
- }
2508
- throw new NoMatch(segmentGroup);
2657
+ return from(routes).pipe(concatMap((r) => {
2658
+ const expanded$ = this.expandSegmentAgainstRoute(ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
2659
+ return expanded$.pipe(catchError((e) => {
2660
+ if (e instanceof NoMatch) {
2661
+ return of(null);
2509
2662
  }
2510
2663
  throw e;
2511
2664
  }));
2512
- };
2513
- const expansions = Array.from(routesByOutlet.entries()).map(([routeOutlet, routes]) => {
2514
- const expanded = expandRoutes(routes);
2515
- // Map all results from outlets we aren't activating to `null` so they can be ignored later
2516
- return routeOutlet === outlet ? expanded :
2517
- expanded.pipe(map(() => null), catchError(() => of(null)));
2518
- });
2519
- return from(expansions)
2520
- .pipe(combineAll(), first(),
2521
- // Return only the expansion for the route outlet we are trying to activate.
2522
- map(results => results.find(result => result !== null)));
2523
- }
2524
- noLeftoversInUrl(segmentGroup, segments, outlet) {
2525
- return segments.length === 0 && !segmentGroup.children[outlet];
2665
+ }), first((s) => !!s), catchError((e, _) => {
2666
+ if (e instanceof EmptyError || e.name === 'EmptyError') {
2667
+ if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
2668
+ return of(new UrlSegmentGroup([], {}));
2669
+ }
2670
+ throw new NoMatch(segmentGroup);
2671
+ }
2672
+ throw e;
2673
+ }));
2526
2674
  }
2527
2675
  expandSegmentAgainstRoute(ngModule, segmentGroup, routes, route, paths, outlet, allowRedirects) {
2528
- // Empty string segments are special because multiple outlets can match a single path, i.e.
2529
- // `[{path: '', component: B}, {path: '', loadChildren: () => {}, outlet: "about"}]`
2530
- if (getOutlet(route) !== outlet && route.path !== '') {
2531
- return noMatch(segmentGroup);
2676
+ if (!isImmediateMatch(route, segmentGroup, paths, outlet)) {
2677
+ return noMatch$1(segmentGroup);
2532
2678
  }
2533
2679
  if (route.redirectTo === undefined) {
2534
- return this.matchSegmentAgainstRoute(ngModule, segmentGroup, route, paths);
2680
+ return this.matchSegmentAgainstRoute(ngModule, segmentGroup, route, paths, outlet);
2535
2681
  }
2536
2682
  if (allowRedirects && this.allowRedirects) {
2537
2683
  return this.expandSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, paths, outlet);
2538
2684
  }
2539
- return noMatch(segmentGroup);
2685
+ return noMatch$1(segmentGroup);
2540
2686
  }
2541
2687
  expandSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, segments, outlet) {
2542
2688
  if (route.path === '**') {
@@ -2557,7 +2703,7 @@ class ApplyRedirects {
2557
2703
  expandRegularSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, segments, outlet) {
2558
2704
  const { matched, consumedSegments, lastChild, positionalParamSegments } = match(segmentGroup, route, segments);
2559
2705
  if (!matched)
2560
- return noMatch(segmentGroup);
2706
+ return noMatch$1(segmentGroup);
2561
2707
  const newTree = this.applyRedirectCommands(consumedSegments, route.redirectTo, positionalParamSegments);
2562
2708
  if (route.redirectTo.startsWith('/')) {
2563
2709
  return absoluteRedirect(newTree);
@@ -2566,7 +2712,7 @@ class ApplyRedirects {
2566
2712
  return this.expandSegment(ngModule, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet, false);
2567
2713
  }));
2568
2714
  }
2569
- matchSegmentAgainstRoute(ngModule, rawSegmentGroup, route, segments) {
2715
+ matchSegmentAgainstRoute(ngModule, rawSegmentGroup, route, segments, outlet) {
2570
2716
  if (route.path === '**') {
2571
2717
  if (route.loadChildren) {
2572
2718
  return this.configLoader.load(ngModule.injector, route)
@@ -2579,13 +2725,15 @@ class ApplyRedirects {
2579
2725
  }
2580
2726
  const { matched, consumedSegments, lastChild } = match(rawSegmentGroup, route, segments);
2581
2727
  if (!matched)
2582
- return noMatch(rawSegmentGroup);
2728
+ return noMatch$1(rawSegmentGroup);
2583
2729
  const rawSlicedSegments = segments.slice(lastChild);
2584
2730
  const childConfig$ = this.getChildConfig(ngModule, route, segments);
2585
2731
  return childConfig$.pipe(mergeMap((routerConfig) => {
2586
2732
  const childModule = routerConfig.module;
2587
2733
  const childConfig = routerConfig.routes;
2588
- const { segmentGroup, slicedSegments } = split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig);
2734
+ const { segmentGroup: splitSegmentGroup, slicedSegments } = split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig);
2735
+ // See comment on the other call to `split` about why this is necessary.
2736
+ const segmentGroup = new UrlSegmentGroup(splitSegmentGroup.segments, splitSegmentGroup.children);
2589
2737
  if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
2590
2738
  const expanded$ = this.expandChildren(childModule, childConfig, segmentGroup);
2591
2739
  return expanded$.pipe(map((children) => new UrlSegmentGroup(consumedSegments, children)));
@@ -2593,7 +2741,8 @@ class ApplyRedirects {
2593
2741
  if (childConfig.length === 0 && slicedSegments.length === 0) {
2594
2742
  return of(new UrlSegmentGroup(consumedSegments, {}));
2595
2743
  }
2596
- const expanded$ = this.expandSegment(childModule, segmentGroup, childConfig, slicedSegments, PRIMARY_OUTLET, true);
2744
+ const matchedOnOutlet = getOutlet(route) === outlet;
2745
+ const expanded$ = this.expandSegment(childModule, segmentGroup, childConfig, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet, true);
2597
2746
  return expanded$.pipe(map((cs) => new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children)));
2598
2747
  }));
2599
2748
  }
@@ -2713,43 +2862,14 @@ class ApplyRedirects {
2713
2862
  return redirectToUrlSegment;
2714
2863
  }
2715
2864
  }
2716
- function match(segmentGroup, route, segments) {
2717
- if (route.path === '') {
2718
- if ((route.pathMatch === 'full') && (segmentGroup.hasChildren() || segments.length > 0)) {
2719
- return { matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {} };
2720
- }
2721
- return { matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {} };
2722
- }
2723
- const matcher = route.matcher || defaultUrlMatcher;
2724
- const res = matcher(segments, segmentGroup, route);
2725
- if (!res) {
2726
- return {
2727
- matched: false,
2728
- consumedSegments: [],
2729
- lastChild: 0,
2730
- positionalParamSegments: {},
2731
- };
2732
- }
2733
- return {
2734
- matched: true,
2735
- consumedSegments: res.consumed,
2736
- lastChild: res.consumed.length,
2737
- positionalParamSegments: res.posParams,
2738
- };
2739
- }
2740
- function split(segmentGroup, consumedSegments, slicedSegments, config) {
2741
- if (slicedSegments.length > 0 &&
2742
- containsEmptyPathRedirectsWithNamedOutlets(segmentGroup, slicedSegments, config)) {
2743
- const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptySegments(config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
2744
- return { segmentGroup: mergeTrivialChildren(s), slicedSegments: [] };
2745
- }
2746
- if (slicedSegments.length === 0 &&
2747
- containsEmptyPathRedirects(segmentGroup, slicedSegments, config)) {
2748
- const s = new UrlSegmentGroup(segmentGroup.segments, addEmptySegmentsToChildrenIfNeeded(segmentGroup, slicedSegments, config, segmentGroup.children));
2749
- return { segmentGroup: mergeTrivialChildren(s), slicedSegments };
2750
- }
2751
- return { segmentGroup, slicedSegments };
2752
- }
2865
+ /**
2866
+ * When possible, merges the primary outlet child into the parent `UrlSegmentGroup`.
2867
+ *
2868
+ * When a segment group has only one child which is a primary outlet, merges that child into the
2869
+ * parent. That is, the child segment group's segments are merged into the `s` and the child's
2870
+ * children become the children of `s`. Think of this like a 'squash', merging the child segment
2871
+ * group into the parent.
2872
+ */
2753
2873
  function mergeTrivialChildren(s) {
2754
2874
  if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) {
2755
2875
  const c = s.children[PRIMARY_OUTLET];
@@ -2757,36 +2877,23 @@ function mergeTrivialChildren(s) {
2757
2877
  }
2758
2878
  return s;
2759
2879
  }
2760
- function addEmptySegmentsToChildrenIfNeeded(segmentGroup, slicedSegments, routes, children) {
2761
- const res = {};
2762
- for (const r of routes) {
2763
- if (isEmptyPathRedirect(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
2764
- res[getOutlet(r)] = new UrlSegmentGroup([], {});
2765
- }
2766
- }
2767
- return Object.assign(Object.assign({}, children), res);
2768
- }
2769
- function createChildrenForEmptySegments(routes, primarySegmentGroup) {
2770
- const res = {};
2771
- res[PRIMARY_OUTLET] = primarySegmentGroup;
2772
- for (const r of routes) {
2773
- if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) {
2774
- res[getOutlet(r)] = new UrlSegmentGroup([], {});
2880
+ /**
2881
+ * Recursively merges primary segment children into their parents and also drops empty children
2882
+ * (those which have no segments and no children themselves). The latter prevents serializing a
2883
+ * group into something like `/a(aux:)`, where `aux` is an empty child segment.
2884
+ */
2885
+ function squashSegmentGroup(segmentGroup) {
2886
+ const newChildren = {};
2887
+ for (const childOutlet of Object.keys(segmentGroup.children)) {
2888
+ const child = segmentGroup.children[childOutlet];
2889
+ const childCandidate = squashSegmentGroup(child);
2890
+ // don't add empty children
2891
+ if (childCandidate.segments.length > 0 || childCandidate.hasChildren()) {
2892
+ newChildren[childOutlet] = childCandidate;
2775
2893
  }
2776
2894
  }
2777
- return res;
2778
- }
2779
- function containsEmptyPathRedirectsWithNamedOutlets(segmentGroup, segments, routes) {
2780
- return routes.some(r => isEmptyPathRedirect(segmentGroup, segments, r) && getOutlet(r) !== PRIMARY_OUTLET);
2781
- }
2782
- function containsEmptyPathRedirects(segmentGroup, segments, routes) {
2783
- return routes.some(r => isEmptyPathRedirect(segmentGroup, segments, r));
2784
- }
2785
- function isEmptyPathRedirect(segmentGroup, segments, r) {
2786
- if ((segmentGroup.hasChildren() || segments.length > 0) && r.pathMatch === 'full') {
2787
- return false;
2788
- }
2789
- return r.path === '' && r.redirectTo !== undefined;
2895
+ const s = new UrlSegmentGroup(segmentGroup.segments, newChildren);
2896
+ return mergeTrivialChildren(s);
2790
2897
  }
2791
2898
 
2792
2899
  /**
@@ -3103,9 +3210,26 @@ function runCanDeactivate(component, currARS, currRSS, futureRSS, moduleInjector
3103
3210
  */
3104
3211
  class NoMatch$1 {
3105
3212
  }
3213
+ function newObservableError(e) {
3214
+ // TODO(atscott): This pattern is used throughout the router code and can be `throwError` instead.
3215
+ return new Observable((obs) => obs.error(e));
3216
+ }
3106
3217
  function recognize(rootComponentType, config, urlTree, url, paramsInheritanceStrategy = 'emptyOnly', relativeLinkResolution = 'legacy') {
3107
- return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution)
3108
- .recognize();
3218
+ try {
3219
+ const result = new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution)
3220
+ .recognize();
3221
+ if (result === null) {
3222
+ return newObservableError(new NoMatch$1());
3223
+ }
3224
+ else {
3225
+ return of(result);
3226
+ }
3227
+ }
3228
+ catch (e) {
3229
+ // Catch the potential error from recognize due to duplicate outlet matches and return as an
3230
+ // `Observable` error instead.
3231
+ return newObservableError(e);
3232
+ }
3109
3233
  }
3110
3234
  class Recognizer {
3111
3235
  constructor(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution) {
@@ -3117,18 +3241,19 @@ class Recognizer {
3117
3241
  this.relativeLinkResolution = relativeLinkResolution;
3118
3242
  }
3119
3243
  recognize() {
3120
- try {
3121
- const rootSegmentGroup = split$1(this.urlTree.root, [], [], this.config, this.relativeLinkResolution).segmentGroup;
3122
- const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET);
3123
- const root = new ActivatedRouteSnapshot([], Object.freeze({}), Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, {}, PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1, {});
3124
- const rootNode = new TreeNode(root, children);
3125
- const routeState = new RouterStateSnapshot(this.url, rootNode);
3126
- this.inheritParamsAndData(routeState._root);
3127
- return of(routeState);
3128
- }
3129
- catch (e) {
3130
- return new Observable((obs) => obs.error(e));
3244
+ const rootSegmentGroup = split(this.urlTree.root, [], [], this.config.filter(c => c.redirectTo === undefined), this.relativeLinkResolution)
3245
+ .segmentGroup;
3246
+ const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET);
3247
+ if (children === null) {
3248
+ return null;
3131
3249
  }
3250
+ // Use Object.freeze to prevent readers of the Router state from modifying it outside of a
3251
+ // navigation, resulting in the router being out of sync with the browser.
3252
+ const root = new ActivatedRouteSnapshot([], Object.freeze({}), Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, {}, PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1, {});
3253
+ const rootNode = new TreeNode(root, children);
3254
+ const routeState = new RouterStateSnapshot(this.url, rootNode);
3255
+ this.inheritParamsAndData(routeState._root);
3256
+ return routeState;
3132
3257
  }
3133
3258
  inheritParamsAndData(routeNode) {
3134
3259
  const route = routeNode.value;
@@ -3143,58 +3268,101 @@ class Recognizer {
3143
3268
  }
3144
3269
  return this.processSegment(config, segmentGroup, segmentGroup.segments, outlet);
3145
3270
  }
3271
+ /**
3272
+ * Matches every child outlet in the `segmentGroup` to a `Route` in the config. Returns `null` if
3273
+ * we cannot find a match for _any_ of the children.
3274
+ *
3275
+ * @param config - The `Routes` to match against
3276
+ * @param segmentGroup - The `UrlSegmentGroup` whose children need to be matched against the
3277
+ * config.
3278
+ */
3146
3279
  processChildren(config, segmentGroup) {
3147
- const children = mapChildrenIntoArray(segmentGroup, (child, childOutlet) => this.processSegmentGroup(config, child, childOutlet));
3148
- checkOutletNameUniqueness(children);
3149
- sortActivatedRouteSnapshots(children);
3150
- return children;
3280
+ const children = [];
3281
+ for (const childOutlet of Object.keys(segmentGroup.children)) {
3282
+ const child = segmentGroup.children[childOutlet];
3283
+ // Sort the config so that routes with outlets that match the one being activated appear
3284
+ // first, followed by routes for other outlets, which might match if they have an empty path.
3285
+ const sortedConfig = sortByMatchingOutlets(config, childOutlet);
3286
+ const outletChildren = this.processSegmentGroup(sortedConfig, child, childOutlet);
3287
+ if (outletChildren === null) {
3288
+ // Configs must match all segment children so because we did not find a match for this
3289
+ // outlet, return `null`.
3290
+ return null;
3291
+ }
3292
+ children.push(...outletChildren);
3293
+ }
3294
+ // Because we may have matched two outlets to the same empty path segment, we can have multiple
3295
+ // activated results for the same outlet. We should merge the children of these results so the
3296
+ // final return value is only one `TreeNode` per outlet.
3297
+ const mergedChildren = mergeEmptyPathMatches(children);
3298
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
3299
+ // This should really never happen - we are only taking the first match for each outlet and
3300
+ // merge the empty path matches.
3301
+ checkOutletNameUniqueness(mergedChildren);
3302
+ }
3303
+ sortActivatedRouteSnapshots(mergedChildren);
3304
+ return mergedChildren;
3151
3305
  }
3152
3306
  processSegment(config, segmentGroup, segments, outlet) {
3153
3307
  for (const r of config) {
3154
- try {
3155
- return this.processSegmentAgainstRoute(r, segmentGroup, segments, outlet);
3156
- }
3157
- catch (e) {
3158
- if (!(e instanceof NoMatch$1))
3159
- throw e;
3308
+ const children = this.processSegmentAgainstRoute(r, segmentGroup, segments, outlet);
3309
+ if (children !== null) {
3310
+ return children;
3160
3311
  }
3161
3312
  }
3162
- if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
3313
+ if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
3163
3314
  return [];
3164
3315
  }
3165
- throw new NoMatch$1();
3166
- }
3167
- noLeftoversInUrl(segmentGroup, segments, outlet) {
3168
- return segments.length === 0 && !segmentGroup.children[outlet];
3316
+ return null;
3169
3317
  }
3170
3318
  processSegmentAgainstRoute(route, rawSegment, segments, outlet) {
3171
- if (route.redirectTo)
3172
- throw new NoMatch$1();
3173
- if ((route.outlet || PRIMARY_OUTLET) !== outlet)
3174
- throw new NoMatch$1();
3319
+ if (route.redirectTo || !isImmediateMatch(route, rawSegment, segments, outlet))
3320
+ return null;
3175
3321
  let snapshot;
3176
3322
  let consumedSegments = [];
3177
3323
  let rawSlicedSegments = [];
3178
3324
  if (route.path === '**') {
3179
3325
  const params = segments.length > 0 ? last(segments).parameters : {};
3180
- snapshot = new ActivatedRouteSnapshot(segments, params, Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, getData(route), outlet, route.component, route, getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + segments.length, getResolve(route));
3326
+ snapshot = new ActivatedRouteSnapshot(segments, params, Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, getData(route), getOutlet(route), route.component, route, getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + segments.length, getResolve(route));
3181
3327
  }
3182
3328
  else {
3183
- const result = match$1(rawSegment, route, segments);
3329
+ const result = match(rawSegment, route, segments);
3330
+ if (!result.matched) {
3331
+ return null;
3332
+ }
3184
3333
  consumedSegments = result.consumedSegments;
3185
3334
  rawSlicedSegments = segments.slice(result.lastChild);
3186
- snapshot = new ActivatedRouteSnapshot(consumedSegments, result.parameters, Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, getData(route), outlet, route.component, route, getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length, getResolve(route));
3335
+ snapshot = new ActivatedRouteSnapshot(consumedSegments, result.parameters, Object.freeze(Object.assign({}, this.urlTree.queryParams)), this.urlTree.fragment, getData(route), getOutlet(route), route.component, route, getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length, getResolve(route));
3187
3336
  }
3188
3337
  const childConfig = getChildConfig(route);
3189
- const { segmentGroup, slicedSegments } = split$1(rawSegment, consumedSegments, rawSlicedSegments, childConfig, this.relativeLinkResolution);
3338
+ const { segmentGroup, slicedSegments } = split(rawSegment, consumedSegments, rawSlicedSegments,
3339
+ // Filter out routes with redirectTo because we are trying to create activated route
3340
+ // snapshots and don't handle redirects here. That should have been done in
3341
+ // `applyRedirects`.
3342
+ childConfig.filter(c => c.redirectTo === undefined), this.relativeLinkResolution);
3190
3343
  if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
3191
3344
  const children = this.processChildren(childConfig, segmentGroup);
3345
+ if (children === null) {
3346
+ return null;
3347
+ }
3192
3348
  return [new TreeNode(snapshot, children)];
3193
3349
  }
3194
3350
  if (childConfig.length === 0 && slicedSegments.length === 0) {
3195
3351
  return [new TreeNode(snapshot, [])];
3196
3352
  }
3197
- const children = this.processSegment(childConfig, segmentGroup, slicedSegments, PRIMARY_OUTLET);
3353
+ const matchedOnOutlet = getOutlet(route) === outlet;
3354
+ // If we matched a config due to empty path match on a different outlet, we need to continue
3355
+ // passing the current outlet for the segment rather than switch to PRIMARY.
3356
+ // Note that we switch to primary when we have a match because outlet configs look like this:
3357
+ // {path: 'a', outlet: 'a', children: [
3358
+ // {path: 'b', component: B},
3359
+ // {path: 'c', component: C},
3360
+ // ]}
3361
+ // Notice that the children of the named outlet are configured with the primary outlet
3362
+ const children = this.processSegment(childConfig, segmentGroup, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet);
3363
+ if (children === null) {
3364
+ return null;
3365
+ }
3198
3366
  return [new TreeNode(snapshot, children)];
3199
3367
  }
3200
3368
  }
@@ -3216,24 +3384,31 @@ function getChildConfig(route) {
3216
3384
  }
3217
3385
  return [];
3218
3386
  }
3219
- function match$1(segmentGroup, route, segments) {
3220
- if (route.path === '') {
3221
- if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
3222
- throw new NoMatch$1();
3387
+ function hasEmptyPathConfig(node) {
3388
+ const config = node.value.routeConfig;
3389
+ return config && config.path === '' && config.redirectTo === undefined;
3390
+ }
3391
+ /**
3392
+ * Finds `TreeNode`s with matching empty path route configs and merges them into `TreeNode` with the
3393
+ * children from each duplicate. This is necessary because different outlets can match a single
3394
+ * empty path route config and the results need to then be merged.
3395
+ */
3396
+ function mergeEmptyPathMatches(nodes) {
3397
+ const result = [];
3398
+ for (const node of nodes) {
3399
+ if (!hasEmptyPathConfig(node)) {
3400
+ result.push(node);
3401
+ continue;
3402
+ }
3403
+ const duplicateEmptyPathNode = result.find(resultNode => node.value.routeConfig === resultNode.value.routeConfig);
3404
+ if (duplicateEmptyPathNode !== undefined) {
3405
+ duplicateEmptyPathNode.children.push(...node.children);
3406
+ }
3407
+ else {
3408
+ result.push(node);
3223
3409
  }
3224
- return { consumedSegments: [], lastChild: 0, parameters: {} };
3225
3410
  }
3226
- const matcher = route.matcher || defaultUrlMatcher;
3227
- const res = matcher(segments, segmentGroup, route);
3228
- if (!res)
3229
- throw new NoMatch$1();
3230
- const posParams = {};
3231
- forEach(res.posParams, (v, k) => {
3232
- posParams[k] = v.path;
3233
- });
3234
- const parameters = res.consumed.length > 0 ? Object.assign(Object.assign({}, posParams), res.consumed[res.consumed.length - 1].parameters) :
3235
- posParams;
3236
- return { consumedSegments: res.consumed, lastChild: res.consumed.length, parameters };
3411
+ return result;
3237
3412
  }
3238
3413
  function checkOutletNameUniqueness(nodes) {
3239
3414
  const names = {};
@@ -3263,70 +3438,6 @@ function getPathIndexShift(segmentGroup) {
3263
3438
  }
3264
3439
  return res - 1;
3265
3440
  }
3266
- function split$1(segmentGroup, consumedSegments, slicedSegments, config, relativeLinkResolution) {
3267
- if (slicedSegments.length > 0 &&
3268
- containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) {
3269
- const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptyPaths(segmentGroup, consumedSegments, config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
3270
- s._sourceSegment = segmentGroup;
3271
- s._segmentIndexShift = consumedSegments.length;
3272
- return { segmentGroup: s, slicedSegments: [] };
3273
- }
3274
- if (slicedSegments.length === 0 &&
3275
- containsEmptyPathMatches(segmentGroup, slicedSegments, config)) {
3276
- const s = new UrlSegmentGroup(segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, config, segmentGroup.children, relativeLinkResolution));
3277
- s._sourceSegment = segmentGroup;
3278
- s._segmentIndexShift = consumedSegments.length;
3279
- return { segmentGroup: s, slicedSegments };
3280
- }
3281
- const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children);
3282
- s._sourceSegment = segmentGroup;
3283
- s._segmentIndexShift = consumedSegments.length;
3284
- return { segmentGroup: s, slicedSegments };
3285
- }
3286
- function addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, routes, children, relativeLinkResolution) {
3287
- const res = {};
3288
- for (const r of routes) {
3289
- if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
3290
- const s = new UrlSegmentGroup([], {});
3291
- s._sourceSegment = segmentGroup;
3292
- if (relativeLinkResolution === 'legacy') {
3293
- s._segmentIndexShift = segmentGroup.segments.length;
3294
- }
3295
- else {
3296
- s._segmentIndexShift = consumedSegments.length;
3297
- }
3298
- res[getOutlet(r)] = s;
3299
- }
3300
- }
3301
- return Object.assign(Object.assign({}, children), res);
3302
- }
3303
- function createChildrenForEmptyPaths(segmentGroup, consumedSegments, routes, primarySegment) {
3304
- const res = {};
3305
- res[PRIMARY_OUTLET] = primarySegment;
3306
- primarySegment._sourceSegment = segmentGroup;
3307
- primarySegment._segmentIndexShift = consumedSegments.length;
3308
- for (const r of routes) {
3309
- if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) {
3310
- const s = new UrlSegmentGroup([], {});
3311
- s._sourceSegment = segmentGroup;
3312
- s._segmentIndexShift = consumedSegments.length;
3313
- res[getOutlet(r)] = s;
3314
- }
3315
- }
3316
- return res;
3317
- }
3318
- function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) {
3319
- return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET);
3320
- }
3321
- function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) {
3322
- return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r));
3323
- }
3324
- function emptyPathMatch(segmentGroup, slicedSegments, r) {
3325
- if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') {
3326
- return false;
3327
- }
3328
- return r.path === '' && r.redirectTo === undefined;
3329
- }
3330
3441
  function getData(route) {
3331
3442
  return route.data || {};
3332
3443
  }
@@ -3997,7 +4108,7 @@ class Router {
3997
4108
  // navigation completes, there will be nothing in
3998
4109
  // history.state.navigationId. This can cause sync problems with AngularJS
3999
4110
  // sync code which looks for a value here in order to determine whether or
4000
- // not to handle a given popstate event or to leave it to the Angualr
4111
+ // not to handle a given popstate event or to leave it to the Angular
4001
4112
  // router.
4002
4113
  this.resetUrlToCurrentUrlTree();
4003
4114
  const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), `Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`);
@@ -5677,7 +5788,7 @@ function provideRouterInitializer() {
5677
5788
  /**
5678
5789
  * @publicApi
5679
5790
  */
5680
- const VERSION = new Version('11.0.4');
5791
+ const VERSION = new Version('11.0.8');
5681
5792
 
5682
5793
  /**
5683
5794
  * @license