@angular/router 11.0.5 → 11.0.9

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.
Files changed (39) hide show
  1. package/bundles/router-testing.umd.js +1 -1
  2. package/bundles/router-testing.umd.min.js +1 -1
  3. package/bundles/router-testing.umd.min.js.map +1 -1
  4. package/bundles/router-upgrade.umd.js +1 -1
  5. package/bundles/router-upgrade.umd.min.js +1 -1
  6. package/bundles/router-upgrade.umd.min.js.map +1 -1
  7. package/bundles/router.umd.js +514 -405
  8. package/bundles/router.umd.js.map +1 -1
  9. package/bundles/router.umd.min.js +19 -26
  10. package/bundles/router.umd.min.js.map +1 -1
  11. package/esm2015/src/apply_redirects.js +90 -117
  12. package/esm2015/src/directives/router_link_active.js +2 -4
  13. package/esm2015/src/operators/activate_routes.js +13 -11
  14. package/esm2015/src/operators/apply_redirects.js +3 -5
  15. package/esm2015/src/operators/check_guards.js +16 -26
  16. package/esm2015/src/operators/prioritized_guard_value.js +2 -2
  17. package/esm2015/src/operators/recognize.js +3 -5
  18. package/esm2015/src/operators/resolve_data.js +10 -12
  19. package/esm2015/src/operators/switch_tap.js +9 -11
  20. package/esm2015/src/recognize.js +126 -122
  21. package/esm2015/src/router.js +8 -12
  22. package/esm2015/src/router_module.js +1 -1
  23. package/esm2015/src/router_state.js +20 -2
  24. package/esm2015/src/url_tree.js +1 -1
  25. package/esm2015/src/utils/collection.js +1 -25
  26. package/esm2015/src/utils/config.js +10 -14
  27. package/esm2015/src/utils/config_matching.js +145 -0
  28. package/esm2015/src/version.js +1 -1
  29. package/fesm2015/router.js +437 -350
  30. package/fesm2015/router.js.map +1 -1
  31. package/fesm2015/testing.js +1 -1
  32. package/fesm2015/upgrade.js +1 -1
  33. package/package.json +4 -4
  34. package/router.d.ts +33 -4
  35. package/router.metadata.json +1 -1
  36. package/testing/testing.d.ts +1 -1
  37. package/testing.d.ts +1 -1
  38. package/upgrade/upgrade.d.ts +1 -1
  39. package/upgrade.d.ts +1 -1
@@ -1,13 +1,13 @@
1
1
  /**
2
- * @license Angular v11.0.5
2
+ * @license Angular v11.0.9
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, concat, defer, EMPTY, Subject } from 'rxjs';
10
+ import { map, switchMap, take, startWith, scan, filter, catchError, concatMap, last as last$1, first, mergeMap, tap, 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) {
@@ -2221,7 +2219,7 @@ function isCanDeactivate(guard) {
2221
2219
  const INITIAL_VALUE = Symbol('INITIAL_VALUE');
2222
2220
  function prioritizedGuardValue() {
2223
2221
  return switchMap(obs => {
2224
- return combineLatest(...obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE))))
2222
+ return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE))))
2225
2223
  .pipe(scan((acc, list) => {
2226
2224
  let isPending = false;
2227
2225
  return list.reduce((innerAcc, val, i) => {
@@ -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
  /**
@@ -2797,10 +2904,8 @@ function isEmptyPathRedirect(segmentGroup, segments, r) {
2797
2904
  * found in the LICENSE file at https://angular.io/license
2798
2905
  */
2799
2906
  function applyRedirects$1(moduleInjector, configLoader, urlSerializer, config) {
2800
- return function (source) {
2801
- return source.pipe(switchMap(t => applyRedirects(moduleInjector, configLoader, urlSerializer, t.extractedUrl, config)
2802
- .pipe(map(urlAfterRedirects => (Object.assign(Object.assign({}, t), { urlAfterRedirects }))))));
2803
- };
2907
+ return switchMap(t => applyRedirects(moduleInjector, configLoader, urlSerializer, t.extractedUrl, config)
2908
+ .pipe(map(urlAfterRedirects => (Object.assign(Object.assign({}, t), { urlAfterRedirects })))));
2804
2909
  }
2805
2910
 
2806
2911
  /**
@@ -2961,20 +3066,18 @@ function deactivateRouteAndItsChildren(route, context, checks) {
2961
3066
  * found in the LICENSE file at https://angular.io/license
2962
3067
  */
2963
3068
  function checkGuards(moduleInjector, forwardEvent) {
2964
- return function (source) {
2965
- return source.pipe(mergeMap(t => {
2966
- const { targetSnapshot, currentSnapshot, guards: { canActivateChecks, canDeactivateChecks } } = t;
2967
- if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
2968
- return of(Object.assign(Object.assign({}, t), { guardsResult: true }));
2969
- }
2970
- return runCanDeactivateChecks(canDeactivateChecks, targetSnapshot, currentSnapshot, moduleInjector)
2971
- .pipe(mergeMap(canDeactivate => {
2972
- return canDeactivate && isBoolean(canDeactivate) ?
2973
- runCanActivateChecks(targetSnapshot, canActivateChecks, moduleInjector, forwardEvent) :
2974
- of(canDeactivate);
2975
- }), map(guardsResult => (Object.assign(Object.assign({}, t), { guardsResult }))));
2976
- }));
2977
- };
3069
+ return mergeMap(t => {
3070
+ const { targetSnapshot, currentSnapshot, guards: { canActivateChecks, canDeactivateChecks } } = t;
3071
+ if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
3072
+ return of(Object.assign(Object.assign({}, t), { guardsResult: true }));
3073
+ }
3074
+ return runCanDeactivateChecks(canDeactivateChecks, targetSnapshot, currentSnapshot, moduleInjector)
3075
+ .pipe(mergeMap(canDeactivate => {
3076
+ return canDeactivate && isBoolean(canDeactivate) ?
3077
+ runCanActivateChecks(targetSnapshot, canActivateChecks, moduleInjector, forwardEvent) :
3078
+ of(canDeactivate);
3079
+ }), map(guardsResult => (Object.assign(Object.assign({}, t), { guardsResult }))));
3080
+ });
2978
3081
  }
2979
3082
  function runCanDeactivateChecks(checks, futureRSS, currRSS, moduleInjector) {
2980
3083
  return from(checks).pipe(mergeMap(check => runCanDeactivate(check.component, check.route, currRSS, futureRSS, moduleInjector)), first(result => {
@@ -2983,15 +3086,7 @@ function runCanDeactivateChecks(checks, futureRSS, currRSS, moduleInjector) {
2983
3086
  }
2984
3087
  function runCanActivateChecks(futureSnapshot, checks, moduleInjector, forwardEvent) {
2985
3088
  return from(checks).pipe(concatMap((check) => {
2986
- return from([
2987
- fireChildActivationStart(check.route.parent, forwardEvent),
2988
- fireActivationStart(check.route, forwardEvent),
2989
- runCanActivateChild(futureSnapshot, check.path, moduleInjector),
2990
- runCanActivate(futureSnapshot, check.route, moduleInjector)
2991
- ])
2992
- .pipe(concatAll(), first(result => {
2993
- return result !== true;
2994
- }, true));
3089
+ return concat(fireChildActivationStart(check.route.parent, forwardEvent), fireActivationStart(check.route, forwardEvent), runCanActivateChild(futureSnapshot, check.path, moduleInjector), runCanActivate(futureSnapshot, check.route, moduleInjector));
2995
3090
  }), first(result => {
2996
3091
  return result !== true;
2997
3092
  }, true));
@@ -3103,9 +3198,26 @@ function runCanDeactivate(component, currARS, currRSS, futureRSS, moduleInjector
3103
3198
  */
3104
3199
  class NoMatch$1 {
3105
3200
  }
3201
+ function newObservableError(e) {
3202
+ // TODO(atscott): This pattern is used throughout the router code and can be `throwError` instead.
3203
+ return new Observable((obs) => obs.error(e));
3204
+ }
3106
3205
  function recognize(rootComponentType, config, urlTree, url, paramsInheritanceStrategy = 'emptyOnly', relativeLinkResolution = 'legacy') {
3107
- return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution)
3108
- .recognize();
3206
+ try {
3207
+ const result = new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution)
3208
+ .recognize();
3209
+ if (result === null) {
3210
+ return newObservableError(new NoMatch$1());
3211
+ }
3212
+ else {
3213
+ return of(result);
3214
+ }
3215
+ }
3216
+ catch (e) {
3217
+ // Catch the potential error from recognize due to duplicate outlet matches and return as an
3218
+ // `Observable` error instead.
3219
+ return newObservableError(e);
3220
+ }
3109
3221
  }
3110
3222
  class Recognizer {
3111
3223
  constructor(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution) {
@@ -3117,18 +3229,19 @@ class Recognizer {
3117
3229
  this.relativeLinkResolution = relativeLinkResolution;
3118
3230
  }
3119
3231
  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));
3232
+ const rootSegmentGroup = split(this.urlTree.root, [], [], this.config.filter(c => c.redirectTo === undefined), this.relativeLinkResolution)
3233
+ .segmentGroup;
3234
+ const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET);
3235
+ if (children === null) {
3236
+ return null;
3131
3237
  }
3238
+ // Use Object.freeze to prevent readers of the Router state from modifying it outside of a
3239
+ // navigation, resulting in the router being out of sync with the browser.
3240
+ 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, {});
3241
+ const rootNode = new TreeNode(root, children);
3242
+ const routeState = new RouterStateSnapshot(this.url, rootNode);
3243
+ this.inheritParamsAndData(routeState._root);
3244
+ return routeState;
3132
3245
  }
3133
3246
  inheritParamsAndData(routeNode) {
3134
3247
  const route = routeNode.value;
@@ -3143,58 +3256,101 @@ class Recognizer {
3143
3256
  }
3144
3257
  return this.processSegment(config, segmentGroup, segmentGroup.segments, outlet);
3145
3258
  }
3259
+ /**
3260
+ * Matches every child outlet in the `segmentGroup` to a `Route` in the config. Returns `null` if
3261
+ * we cannot find a match for _any_ of the children.
3262
+ *
3263
+ * @param config - The `Routes` to match against
3264
+ * @param segmentGroup - The `UrlSegmentGroup` whose children need to be matched against the
3265
+ * config.
3266
+ */
3146
3267
  processChildren(config, segmentGroup) {
3147
- const children = mapChildrenIntoArray(segmentGroup, (child, childOutlet) => this.processSegmentGroup(config, child, childOutlet));
3148
- checkOutletNameUniqueness(children);
3149
- sortActivatedRouteSnapshots(children);
3150
- return children;
3268
+ const children = [];
3269
+ for (const childOutlet of Object.keys(segmentGroup.children)) {
3270
+ const child = segmentGroup.children[childOutlet];
3271
+ // Sort the config so that routes with outlets that match the one being activated appear
3272
+ // first, followed by routes for other outlets, which might match if they have an empty path.
3273
+ const sortedConfig = sortByMatchingOutlets(config, childOutlet);
3274
+ const outletChildren = this.processSegmentGroup(sortedConfig, child, childOutlet);
3275
+ if (outletChildren === null) {
3276
+ // Configs must match all segment children so because we did not find a match for this
3277
+ // outlet, return `null`.
3278
+ return null;
3279
+ }
3280
+ children.push(...outletChildren);
3281
+ }
3282
+ // Because we may have matched two outlets to the same empty path segment, we can have multiple
3283
+ // activated results for the same outlet. We should merge the children of these results so the
3284
+ // final return value is only one `TreeNode` per outlet.
3285
+ const mergedChildren = mergeEmptyPathMatches(children);
3286
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
3287
+ // This should really never happen - we are only taking the first match for each outlet and
3288
+ // merge the empty path matches.
3289
+ checkOutletNameUniqueness(mergedChildren);
3290
+ }
3291
+ sortActivatedRouteSnapshots(mergedChildren);
3292
+ return mergedChildren;
3151
3293
  }
3152
3294
  processSegment(config, segmentGroup, segments, outlet) {
3153
3295
  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;
3296
+ const children = this.processSegmentAgainstRoute(r, segmentGroup, segments, outlet);
3297
+ if (children !== null) {
3298
+ return children;
3160
3299
  }
3161
3300
  }
3162
- if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
3301
+ if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
3163
3302
  return [];
3164
3303
  }
3165
- throw new NoMatch$1();
3166
- }
3167
- noLeftoversInUrl(segmentGroup, segments, outlet) {
3168
- return segments.length === 0 && !segmentGroup.children[outlet];
3304
+ return null;
3169
3305
  }
3170
3306
  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();
3307
+ if (route.redirectTo || !isImmediateMatch(route, rawSegment, segments, outlet))
3308
+ return null;
3175
3309
  let snapshot;
3176
3310
  let consumedSegments = [];
3177
3311
  let rawSlicedSegments = [];
3178
3312
  if (route.path === '**') {
3179
3313
  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));
3314
+ 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
3315
  }
3182
3316
  else {
3183
- const result = match$1(rawSegment, route, segments);
3317
+ const result = match(rawSegment, route, segments);
3318
+ if (!result.matched) {
3319
+ return null;
3320
+ }
3184
3321
  consumedSegments = result.consumedSegments;
3185
3322
  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));
3323
+ 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
3324
  }
3188
3325
  const childConfig = getChildConfig(route);
3189
- const { segmentGroup, slicedSegments } = split$1(rawSegment, consumedSegments, rawSlicedSegments, childConfig, this.relativeLinkResolution);
3326
+ const { segmentGroup, slicedSegments } = split(rawSegment, consumedSegments, rawSlicedSegments,
3327
+ // Filter out routes with redirectTo because we are trying to create activated route
3328
+ // snapshots and don't handle redirects here. That should have been done in
3329
+ // `applyRedirects`.
3330
+ childConfig.filter(c => c.redirectTo === undefined), this.relativeLinkResolution);
3190
3331
  if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
3191
3332
  const children = this.processChildren(childConfig, segmentGroup);
3333
+ if (children === null) {
3334
+ return null;
3335
+ }
3192
3336
  return [new TreeNode(snapshot, children)];
3193
3337
  }
3194
3338
  if (childConfig.length === 0 && slicedSegments.length === 0) {
3195
3339
  return [new TreeNode(snapshot, [])];
3196
3340
  }
3197
- const children = this.processSegment(childConfig, segmentGroup, slicedSegments, PRIMARY_OUTLET);
3341
+ const matchedOnOutlet = getOutlet(route) === outlet;
3342
+ // If we matched a config due to empty path match on a different outlet, we need to continue
3343
+ // passing the current outlet for the segment rather than switch to PRIMARY.
3344
+ // Note that we switch to primary when we have a match because outlet configs look like this:
3345
+ // {path: 'a', outlet: 'a', children: [
3346
+ // {path: 'b', component: B},
3347
+ // {path: 'c', component: C},
3348
+ // ]}
3349
+ // Notice that the children of the named outlet are configured with the primary outlet
3350
+ const children = this.processSegment(childConfig, segmentGroup, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet);
3351
+ if (children === null) {
3352
+ return null;
3353
+ }
3198
3354
  return [new TreeNode(snapshot, children)];
3199
3355
  }
3200
3356
  }
@@ -3216,24 +3372,31 @@ function getChildConfig(route) {
3216
3372
  }
3217
3373
  return [];
3218
3374
  }
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();
3375
+ function hasEmptyPathConfig(node) {
3376
+ const config = node.value.routeConfig;
3377
+ return config && config.path === '' && config.redirectTo === undefined;
3378
+ }
3379
+ /**
3380
+ * Finds `TreeNode`s with matching empty path route configs and merges them into `TreeNode` with the
3381
+ * children from each duplicate. This is necessary because different outlets can match a single
3382
+ * empty path route config and the results need to then be merged.
3383
+ */
3384
+ function mergeEmptyPathMatches(nodes) {
3385
+ const result = [];
3386
+ for (const node of nodes) {
3387
+ if (!hasEmptyPathConfig(node)) {
3388
+ result.push(node);
3389
+ continue;
3390
+ }
3391
+ const duplicateEmptyPathNode = result.find(resultNode => node.value.routeConfig === resultNode.value.routeConfig);
3392
+ if (duplicateEmptyPathNode !== undefined) {
3393
+ duplicateEmptyPathNode.children.push(...node.children);
3394
+ }
3395
+ else {
3396
+ result.push(node);
3223
3397
  }
3224
- return { consumedSegments: [], lastChild: 0, parameters: {} };
3225
3398
  }
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 };
3399
+ return result;
3237
3400
  }
3238
3401
  function checkOutletNameUniqueness(nodes) {
3239
3402
  const names = {};
@@ -3263,70 +3426,6 @@ function getPathIndexShift(segmentGroup) {
3263
3426
  }
3264
3427
  return res - 1;
3265
3428
  }
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
3429
  function getData(route) {
3331
3430
  return route.data || {};
3332
3431
  }
@@ -3342,10 +3441,8 @@ function getResolve(route) {
3342
3441
  * found in the LICENSE file at https://angular.io/license
3343
3442
  */
3344
3443
  function recognize$1(rootComponentType, config, serializer, paramsInheritanceStrategy, relativeLinkResolution) {
3345
- return function (source) {
3346
- return source.pipe(mergeMap(t => recognize(rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects), paramsInheritanceStrategy, relativeLinkResolution)
3347
- .pipe(map(targetSnapshot => (Object.assign(Object.assign({}, t), { targetSnapshot }))))));
3348
- };
3444
+ return mergeMap(t => recognize(rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects), paramsInheritanceStrategy, relativeLinkResolution)
3445
+ .pipe(map(targetSnapshot => (Object.assign(Object.assign({}, t), { targetSnapshot })))));
3349
3446
  }
3350
3447
 
3351
3448
  /**
@@ -3356,17 +3453,15 @@ function recognize$1(rootComponentType, config, serializer, paramsInheritanceStr
3356
3453
  * found in the LICENSE file at https://angular.io/license
3357
3454
  */
3358
3455
  function resolveData(paramsInheritanceStrategy, moduleInjector) {
3359
- return function (source) {
3360
- return source.pipe(mergeMap(t => {
3361
- const { targetSnapshot, guards: { canActivateChecks } } = t;
3362
- if (!canActivateChecks.length) {
3363
- return of(t);
3364
- }
3365
- let canActivateChecksResolved = 0;
3366
- return from(canActivateChecks)
3367
- .pipe(concatMap(check => runResolve(check.route, targetSnapshot, paramsInheritanceStrategy, moduleInjector)), tap(() => canActivateChecksResolved++), takeLast(1), mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY));
3368
- }));
3369
- };
3456
+ return mergeMap(t => {
3457
+ const { targetSnapshot, guards: { canActivateChecks } } = t;
3458
+ if (!canActivateChecks.length) {
3459
+ return of(t);
3460
+ }
3461
+ let canActivateChecksResolved = 0;
3462
+ return from(canActivateChecks)
3463
+ .pipe(concatMap(check => runResolve(check.route, targetSnapshot, paramsInheritanceStrategy, moduleInjector)), tap(() => canActivateChecksResolved++), takeLast(1), mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY));
3464
+ });
3370
3465
  }
3371
3466
  function runResolve(futureARS, futureRSS, paramsInheritanceStrategy, moduleInjector) {
3372
3467
  const resolve = futureARS._resolve;
@@ -3415,15 +3510,13 @@ function getResolver(injectionToken, futureARS, futureRSS, moduleInjector) {
3415
3510
  * it will wait before continuing with the original value.
3416
3511
  */
3417
3512
  function switchTap(next) {
3418
- return function (source) {
3419
- return source.pipe(switchMap(v => {
3420
- const nextResult = next(v);
3421
- if (nextResult) {
3422
- return from(nextResult).pipe(map(() => v));
3423
- }
3424
- return from([v]);
3425
- }));
3426
- };
3513
+ return switchMap(v => {
3514
+ const nextResult = next(v);
3515
+ if (nextResult) {
3516
+ return from(nextResult).pipe(map(() => v));
3517
+ }
3518
+ return of(v);
3519
+ });
3427
3520
  }
3428
3521
 
3429
3522
  /**
@@ -3768,7 +3861,7 @@ class Router {
3768
3861
  this.ngModule = injector.get(NgModuleRef);
3769
3862
  this.console = injector.get(ɵConsole);
3770
3863
  const ngZone = injector.get(NgZone);
3771
- this.isNgZoneEnabled = ngZone instanceof NgZone;
3864
+ this.isNgZoneEnabled = ngZone instanceof NgZone && NgZone.isInAngularZone();
3772
3865
  this.resetConfig(config);
3773
3866
  this.currentUrlTree = createEmptyUrlTree();
3774
3867
  this.rawUrlTree = this.currentUrlTree;
@@ -3833,11 +3926,10 @@ class Router {
3833
3926
  if (transition !== this.transitions.getValue()) {
3834
3927
  return EMPTY;
3835
3928
  }
3836
- return [t];
3929
+ // This delay is required to match old behavior that forced
3930
+ // navigation to always be async
3931
+ return Promise.resolve(t);
3837
3932
  }),
3838
- // This delay is required to match old behavior that forced navigation
3839
- // to always be async
3840
- switchMap(t => Promise.resolve(t)),
3841
3933
  // ApplyRedirects
3842
3934
  applyRedirects$1(this.ngModule.injector, this.configLoader, this.urlSerializer, this.config),
3843
3935
  // Update the currentNavigation
@@ -3854,9 +3946,7 @@ class Router {
3854
3946
  }
3855
3947
  this.browserUrlTree = t.urlAfterRedirects;
3856
3948
  }
3857
- }),
3858
- // Fire RoutesRecognized
3859
- tap(t => {
3949
+ // Fire RoutesRecognized
3860
3950
  const routesRecognized = new RoutesRecognized(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot);
3861
3951
  eventsSubject.next(routesRecognized);
3862
3952
  }));
@@ -3908,7 +3998,6 @@ class Router {
3908
3998
  error.url = t.guardsResult;
3909
3999
  throw error;
3910
4000
  }
3911
- }), tap(t => {
3912
4001
  const guardsEnd = new GuardsCheckEnd(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot, !!t.guardsResult);
3913
4002
  this.triggerEvent(guardsEnd);
3914
4003
  }), filter(t => {
@@ -3997,7 +4086,7 @@ class Router {
3997
4086
  // navigation completes, there will be nothing in
3998
4087
  // history.state.navigationId. This can cause sync problems with AngularJS
3999
4088
  // 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
4089
+ // not to handle a given popstate event or to leave it to the Angular
4001
4090
  // router.
4002
4091
  this.resetUrlToCurrentUrlTree();
4003
4092
  const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), `Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`);
@@ -4041,7 +4130,7 @@ class Router {
4041
4130
  skipLocationChange: t.extras.skipLocationChange,
4042
4131
  replaceUrl: this.urlUpdateStrategy === 'eager'
4043
4132
  };
4044
- return this.scheduleNavigation(mergedTree, 'imperative', null, extras, { resolve: t.resolve, reject: t.reject, promise: t.promise });
4133
+ this.scheduleNavigation(mergedTree, 'imperative', null, extras, { resolve: t.resolve, reject: t.reject, promise: t.promise });
4045
4134
  }, 0);
4046
4135
  }
4047
4136
  /* All other errors should reset to the router's internal URL reference to
@@ -4850,9 +4939,7 @@ class RouterLinkActive {
4850
4939
  /** @nodoc */
4851
4940
  ngAfterContentInit() {
4852
4941
  // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
4853
- from([this.links.changes, this.linksWithHrefs.changes, of(null)])
4854
- .pipe(mergeAll())
4855
- .subscribe(_ => {
4942
+ of(this.links.changes, this.linksWithHrefs.changes, of(null)).pipe(mergeAll()).subscribe(_ => {
4856
4943
  this.update();
4857
4944
  this.subscribeToEachLinkOnChanges();
4858
4945
  });
@@ -5677,7 +5764,7 @@ function provideRouterInitializer() {
5677
5764
  /**
5678
5765
  * @publicApi
5679
5766
  */
5680
- const VERSION = new Version('11.0.5');
5767
+ const VERSION = new Version('11.0.9');
5681
5768
 
5682
5769
  /**
5683
5770
  * @license