Fix router to ensure that a route module is only loaded once especially in relation to the use of preload strategies with delayed or partial loading. Add test to check the interaction of PreloadingStrategy and normal router navigation under differing scenarios. Checking: * Prevention of duplicate loading of modules. related to #26557 * Prevention of duplicate RouteConfigLoad(Start|End) events related to #22842 * Ensuring preload strategy remains active for submodules if needed The selected preload strategy should still decide when to load submodules * Possibility of memory leak with unfinished preload subscription related to #26557 * Ensure that the stored loader promise is cleared so that subsequent load will try the fetch again. * Add error handle error from loadChildren * Ensure we handle error from with NgModule create Fixes #26557 #22842 #26557 PR Close #40389
496 lines
14 KiB
TypeScript
496 lines
14 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {NgModuleFactory, NgModuleRef, Type} from '@angular/core';
|
|
import {Observable} from 'rxjs';
|
|
|
|
import {ActivatedRouteSnapshot} from './router_state';
|
|
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
|
|
|
|
|
/**
|
|
* Represents a route configuration for the Router service.
|
|
* An array of `Route` objects, used in `Router.config` and for nested route configurations
|
|
* in `Route.children`.
|
|
*
|
|
* @see `Route`
|
|
* @see `Router`
|
|
* @see [Router configuration guide](guide/router#configuration)
|
|
* @publicApi
|
|
*/
|
|
export type Routes = Route[];
|
|
|
|
/**
|
|
* Represents the result of matching URLs with a custom matching function.
|
|
*
|
|
* * `consumed` is an array of the consumed URL segments.
|
|
* * `posParams` is a map of positional parameters.
|
|
*
|
|
* @see `UrlMatcher()`
|
|
* @publicApi
|
|
*/
|
|
export type UrlMatchResult = {
|
|
consumed: UrlSegment[];
|
|
posParams?: {[name: string]: UrlSegment};
|
|
};
|
|
|
|
/**
|
|
* A function for matching a route against URLs. Implement a custom URL matcher
|
|
* for `Route.matcher` when a combination of `path` and `pathMatch`
|
|
* is not expressive enough. Cannot be used together with `path` and `pathMatch`.
|
|
*
|
|
* The function takes the following arguments and returns a `UrlMatchResult` object.
|
|
* * *segments* : An array of URL segments.
|
|
* * *group* : A segment group.
|
|
* * *route* : The route to match against.
|
|
*
|
|
* The following example implementation matches HTML files.
|
|
*
|
|
* ```
|
|
* export function htmlFiles(url: UrlSegment[]) {
|
|
* return url.length === 1 && url[0].path.endsWith('.html') ? ({consumed: url}) : null;
|
|
* }
|
|
*
|
|
* export const routes = [{ matcher: htmlFiles, component: AnyComponent }];
|
|
* ```
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export type UrlMatcher = (segments: UrlSegment[], group: UrlSegmentGroup, route: Route) =>
|
|
UrlMatchResult|null;
|
|
|
|
/**
|
|
*
|
|
* Represents static data associated with a particular route.
|
|
*
|
|
* @see `Route#data`
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export type Data = {
|
|
[name: string]: any
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Represents the resolved data associated with a particular route.
|
|
*
|
|
* @see `Route#resolve`.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export type ResolveData = {
|
|
[name: string]: any
|
|
};
|
|
|
|
/**
|
|
*
|
|
* A function that is called to resolve a collection of lazy-loaded routes.
|
|
* Must be an arrow function of the following form:
|
|
* `() => import('...').then(mod => mod.MODULE)`
|
|
*
|
|
* For example:
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'lazy',
|
|
* loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
|
|
* }];
|
|
* ```
|
|
*
|
|
* @see [Route.loadChildren](api/router/Route#loadChildren)
|
|
* @publicApi
|
|
*/
|
|
export type LoadChildrenCallback = () => Type<any>|NgModuleFactory<any>|Observable<Type<any>>|
|
|
Promise<NgModuleFactory<any>|Type<any>|any>;
|
|
|
|
/**
|
|
*
|
|
* A function that returns a set of routes to load.
|
|
*
|
|
* The string form of `LoadChildren` is deprecated (see `DeprecatedLoadChildren`). The function
|
|
* form (`LoadChildrenCallback`) should be used instead.
|
|
*
|
|
* @see `loadChildrenCallback`
|
|
* @publicApi
|
|
*/
|
|
export type LoadChildren = LoadChildrenCallback|DeprecatedLoadChildren;
|
|
|
|
/**
|
|
* A string of the form `path/to/file#exportName` that acts as a URL for a set of routes to load.
|
|
*
|
|
* @see `loadChildrenCallback`
|
|
* @publicApi
|
|
* @deprecated The `string` form of `loadChildren` is deprecated in favor of the
|
|
* `LoadChildrenCallback` function which uses the ES dynamic `import()` expression.
|
|
* This offers a more natural and standards-based mechanism to dynamically
|
|
* load an ES module at runtime.
|
|
*/
|
|
export type DeprecatedLoadChildren = string;
|
|
|
|
/**
|
|
*
|
|
* How to handle query parameters in a router link.
|
|
* One of:
|
|
* - `merge` : Merge new with current parameters.
|
|
* - `preserve` : Preserve current parameters.
|
|
*
|
|
* @see `UrlCreationOptions#queryParamsHandling`
|
|
* @see `RouterLink`
|
|
* @publicApi
|
|
*/
|
|
export type QueryParamsHandling = 'merge'|'preserve'|'';
|
|
|
|
/**
|
|
*
|
|
* A policy for when to run guards and resolvers on a route.
|
|
*
|
|
* @see [Route.runGuardsAndResolvers](api/router/Route#runGuardsAndResolvers)
|
|
* @publicApi
|
|
*/
|
|
export type RunGuardsAndResolvers =
|
|
'pathParamsChange'|'pathParamsOrQueryParamsChange'|'paramsChange'|'paramsOrQueryParamsChange'|
|
|
'always'|((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);
|
|
|
|
/**
|
|
* A configuration object that defines a single route.
|
|
* A set of routes are collected in a `Routes` array to define a `Router` configuration.
|
|
* The router attempts to match segments of a given URL against each route,
|
|
* using the configuration options defined in this object.
|
|
*
|
|
* Supports static, parameterized, redirect, and wildcard routes, as well as
|
|
* custom route data and resolve methods.
|
|
*
|
|
* For detailed usage information, see the [Routing Guide](guide/router).
|
|
*
|
|
* @usageNotes
|
|
*
|
|
* ### Simple Configuration
|
|
*
|
|
* The following route specifies that when navigating to, for example,
|
|
* `/team/11/user/bob`, the router creates the 'Team' component
|
|
* with the 'User' child component in it.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'team/:id',
|
|
* component: Team,
|
|
* children: [{
|
|
* path: 'user/:name',
|
|
* component: User
|
|
* }]
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Multiple Outlets
|
|
*
|
|
* The following route creates sibling components with multiple outlets.
|
|
* When navigating to `/team/11(aux:chat/jim)`, the router creates the 'Team' component next to
|
|
* the 'Chat' component. The 'Chat' component is placed into the 'aux' outlet.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'team/:id',
|
|
* component: Team
|
|
* }, {
|
|
* path: 'chat/:user',
|
|
* component: Chat
|
|
* outlet: 'aux'
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Wild Cards
|
|
*
|
|
* The following route uses wild-card notation to specify a component
|
|
* that is always instantiated regardless of where you navigate to.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: '**',
|
|
* component: WildcardComponent
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Redirects
|
|
*
|
|
* The following route uses the `redirectTo` property to ignore a segment of
|
|
* a given URL when looking for a child path.
|
|
*
|
|
* When navigating to '/team/11/legacy/user/jim', the router changes the URL segment
|
|
* '/team/11/legacy/user/jim' to '/team/11/user/jim', and then instantiates
|
|
* the Team component with the User child component in it.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'team/:id',
|
|
* component: Team,
|
|
* children: [{
|
|
* path: 'legacy/user/:name',
|
|
* redirectTo: 'user/:name'
|
|
* }, {
|
|
* path: 'user/:name',
|
|
* component: User
|
|
* }]
|
|
* }]
|
|
* ```
|
|
*
|
|
* The redirect path can be relative, as shown in this example, or absolute.
|
|
* If we change the `redirectTo` value in the example to the absolute URL segment '/user/:name',
|
|
* the result URL is also absolute, '/user/jim'.
|
|
|
|
* ### Empty Path
|
|
*
|
|
* Empty-path route configurations can be used to instantiate components that do not 'consume'
|
|
* any URL segments.
|
|
*
|
|
* In the following configuration, when navigating to
|
|
* `/team/11`, the router instantiates the 'AllUsers' component.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'team/:id',
|
|
* component: Team,
|
|
* children: [{
|
|
* path: '',
|
|
* component: AllUsers
|
|
* }, {
|
|
* path: 'user/:name',
|
|
* component: User
|
|
* }]
|
|
* }]
|
|
* ```
|
|
*
|
|
* Empty-path routes can have children. In the following example, when navigating
|
|
* to `/team/11/user/jim`, the router instantiates the wrapper component with
|
|
* the user component in it.
|
|
*
|
|
* Note that an empty path route inherits its parent's parameters and data.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'team/:id',
|
|
* component: Team,
|
|
* children: [{
|
|
* path: '',
|
|
* component: WrapperCmp,
|
|
* children: [{
|
|
* path: 'user/:name',
|
|
* component: User
|
|
* }]
|
|
* }]
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Matching Strategy
|
|
*
|
|
* The default path-match strategy is 'prefix', which means that the router
|
|
* checks URL elements from the left to see if the URL matches a specified path.
|
|
* For example, '/team/11/user' matches 'team/:id'.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: '',
|
|
* pathMatch: 'prefix', //default
|
|
* redirectTo: 'main'
|
|
* }, {
|
|
* path: 'main',
|
|
* component: Main
|
|
* }]
|
|
* ```
|
|
*
|
|
* You can specify the path-match strategy 'full' to make sure that the path
|
|
* covers the whole unconsumed URL. It is important to do this when redirecting
|
|
* empty-path routes. Otherwise, because an empty path is a prefix of any URL,
|
|
* the router would apply the redirect even when navigating to the redirect destination,
|
|
* creating an endless loop.
|
|
*
|
|
* In the following example, supplying the 'full' `pathMatch` strategy ensures
|
|
* that the router applies the redirect if and only if navigating to '/'.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: '',
|
|
* pathMatch: 'full',
|
|
* redirectTo: 'main'
|
|
* }, {
|
|
* path: 'main',
|
|
* component: Main
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Componentless Routes
|
|
*
|
|
* You can share parameters between sibling components.
|
|
* For example, suppose that two sibling components should go next to each other,
|
|
* and both of them require an ID parameter. You can accomplish this using a route
|
|
* that does not specify a component at the top level.
|
|
*
|
|
* In the following example, 'MainChild' and 'AuxChild' are siblings.
|
|
* When navigating to 'parent/10/(a//aux:b)', the route instantiates
|
|
* the main child and aux child components next to each other.
|
|
* For this to work, the application component must have the primary and aux outlets defined.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'parent/:id',
|
|
* children: [
|
|
* { path: 'a', component: MainChild },
|
|
* { path: 'b', component: AuxChild, outlet: 'aux' }
|
|
* ]
|
|
* }]
|
|
* ```
|
|
*
|
|
* The router merges the parameters, data, and resolve of the componentless
|
|
* parent into the parameters, data, and resolve of the children.
|
|
*
|
|
* This is especially useful when child components are defined
|
|
* with an empty path string, as in the following example.
|
|
* With this configuration, navigating to '/parent/10' creates
|
|
* the main child and aux components.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'parent/:id',
|
|
* children: [
|
|
* { path: '', component: MainChild },
|
|
* { path: '', component: AuxChild, outlet: 'aux' }
|
|
* ]
|
|
* }]
|
|
* ```
|
|
*
|
|
* ### Lazy Loading
|
|
*
|
|
* Lazy loading speeds up application load time by splitting the application
|
|
* into multiple bundles and loading them on demand.
|
|
* To use lazy loading, provide the `loadChildren` property in the `Route` object,
|
|
* instead of the `children` property.
|
|
*
|
|
* Given the following example route, the router will lazy load
|
|
* the associated module on demand using the browser native import system.
|
|
*
|
|
* ```
|
|
* [{
|
|
* path: 'lazy',
|
|
* loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
|
|
* }];
|
|
* ```
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export interface Route {
|
|
/**
|
|
* The path to match against. Cannot be used together with a custom `matcher` function.
|
|
* A URL string that uses router matching notation.
|
|
* Can be a wild card (`**`) that matches any URL (see Usage Notes below).
|
|
* Default is "/" (the root path).
|
|
*
|
|
*/
|
|
path?: string;
|
|
/**
|
|
* The path-matching strategy, one of 'prefix' or 'full'.
|
|
* Default is 'prefix'.
|
|
*
|
|
* By default, the router checks URL elements from the left to see if the URL
|
|
* matches a given path, and stops when there is a match. For example,
|
|
* '/team/11/user' matches 'team/:id'.
|
|
*
|
|
* The path-match strategy 'full' matches against the entire URL.
|
|
* It is important to do this when redirecting empty-path routes.
|
|
* Otherwise, because an empty path is a prefix of any URL,
|
|
* the router would apply the redirect even when navigating
|
|
* to the redirect destination, creating an endless loop.
|
|
*
|
|
*/
|
|
pathMatch?: string;
|
|
/**
|
|
* A custom URL-matching function. Cannot be used together with `path`.
|
|
*/
|
|
matcher?: UrlMatcher;
|
|
/**
|
|
* The component to instantiate when the path matches.
|
|
* Can be empty if child routes specify components.
|
|
*/
|
|
component?: Type<any>;
|
|
/**
|
|
* A URL to redirect to when the path matches.
|
|
* Absolute if the URL begins with a slash (/), otherwise relative to the path URL.
|
|
* When not present, router does not redirect.
|
|
*/
|
|
redirectTo?: string;
|
|
/**
|
|
* Name of a `RouterOutlet` object where the component can be placed
|
|
* when the path matches.
|
|
*/
|
|
outlet?: string;
|
|
/**
|
|
* An array of dependency-injection tokens used to look up `CanActivate()`
|
|
* handlers, in order to determine if the current user is allowed to
|
|
* activate the component. By default, any user can activate.
|
|
*/
|
|
canActivate?: any[];
|
|
/**
|
|
* An array of DI tokens used to look up `CanActivateChild()` handlers,
|
|
* in order to determine if the current user is allowed to activate
|
|
* a child of the component. By default, any user can activate a child.
|
|
*/
|
|
canActivateChild?: any[];
|
|
/**
|
|
* An array of DI tokens used to look up `CanDeactivate()`
|
|
* handlers, in order to determine if the current user is allowed to
|
|
* deactivate the component. By default, any user can deactivate.
|
|
*
|
|
*/
|
|
canDeactivate?: any[];
|
|
/**
|
|
* An array of DI tokens used to look up `CanLoad()`
|
|
* handlers, in order to determine if the current user is allowed to
|
|
* load the component. By default, any user can load.
|
|
*/
|
|
canLoad?: any[];
|
|
/**
|
|
* Additional developer-defined data provided to the component via
|
|
* `ActivatedRoute`. By default, no additional data is passed.
|
|
*/
|
|
data?: Data;
|
|
/**
|
|
* A map of DI tokens used to look up data resolvers. See `Resolve`.
|
|
*/
|
|
resolve?: ResolveData;
|
|
/**
|
|
* An array of child `Route` objects that specifies a nested route
|
|
* configuration.
|
|
*/
|
|
children?: Routes;
|
|
/**
|
|
* An object specifying lazy-loaded child routes.
|
|
*/
|
|
loadChildren?: LoadChildren;
|
|
/**
|
|
* Defines when guards and resolvers will be run. One of
|
|
* - `paramsOrQueryParamsChange` : Run when query parameters change.
|
|
* - `always` : Run on every execution.
|
|
* By default, guards and resolvers run only when the matrix
|
|
* parameters of the route change.
|
|
*/
|
|
runGuardsAndResolvers?: RunGuardsAndResolvers;
|
|
/**
|
|
* Filled for routes with `loadChildren` once the module has been loaded
|
|
* @internal
|
|
*/
|
|
_loadedConfig?: LoadedRouterConfig;
|
|
/**
|
|
* Filled for routes with `loadChildren` during load
|
|
* @internal
|
|
*/
|
|
_loader$?: Observable<LoadedRouterConfig>;
|
|
}
|
|
|
|
export class LoadedRouterConfig {
|
|
constructor(public routes: Route[], public module: NgModuleRef<any>) {}
|
|
}
|