2020-10-05 12:56:27 +00:00
|
|
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-11-30 16:25:48 +00:00
|
|
|
import { InjectionToken, Injector, ModuleWithProviders, NgModule } from '@angular/core';
|
2021-01-19 15:28:37 +00:00
|
|
|
import {
|
|
|
|
PreloadAllModules,
|
|
|
|
RouterModule,
|
|
|
|
Route,
|
|
|
|
Routes,
|
|
|
|
ROUTES,
|
|
|
|
UrlMatcher,
|
|
|
|
UrlMatchResult,
|
|
|
|
UrlSegment,
|
|
|
|
UrlSegmentGroup,
|
|
|
|
} from '@angular/router';
|
2020-11-30 16:25:48 +00:00
|
|
|
|
|
|
|
import { CoreArray } from '@singletons/array';
|
2021-02-02 17:41:58 +00:00
|
|
|
import { CoreRedirectGuard } from '@guards/redirect';
|
2020-11-30 16:25:48 +00:00
|
|
|
|
2021-01-19 15:28:37 +00:00
|
|
|
/**
|
|
|
|
* Build app routes.
|
|
|
|
*
|
|
|
|
* @param injector Module injector.
|
|
|
|
* @return App routes.
|
|
|
|
*/
|
2020-11-30 16:25:48 +00:00
|
|
|
function buildAppRoutes(injector: Injector): Routes {
|
2021-02-02 17:41:58 +00:00
|
|
|
const appRoutes = CoreArray.flatten(injector.get<Routes[]>(APP_ROUTES, []));
|
|
|
|
|
|
|
|
return appRoutes.map(route => {
|
|
|
|
route.canLoad = route.canLoad ?? [];
|
|
|
|
route.canActivate = route.canActivate ?? [];
|
|
|
|
route.canLoad.push(CoreRedirectGuard);
|
|
|
|
route.canActivate.push(CoreRedirectGuard);
|
|
|
|
|
|
|
|
return route;
|
|
|
|
});
|
2020-11-30 16:25:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-19 15:28:37 +00:00
|
|
|
/**
|
|
|
|
* Create a url matcher that will only match when a given condition is met.
|
|
|
|
*
|
2021-01-26 08:25:54 +00:00
|
|
|
* @param pathOrMatcher Original path or matcher configured in the route.
|
2021-01-19 15:28:37 +00:00
|
|
|
* @param condition Condition.
|
|
|
|
* @return Conditional url matcher.
|
|
|
|
*/
|
2021-01-26 08:25:54 +00:00
|
|
|
function buildConditionalUrlMatcher(pathOrMatcher: string | UrlMatcher, condition: () => boolean): UrlMatcher {
|
2021-01-19 15:28:37 +00:00
|
|
|
// Create a matcher based on Angular's default matcher.
|
|
|
|
// see https://github.com/angular/angular/blob/10.0.x/packages/router/src/shared.ts#L127
|
|
|
|
return (segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult | null => {
|
|
|
|
// If the condition isn't met, the route will never match.
|
|
|
|
if (!condition()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-01-26 08:25:54 +00:00
|
|
|
// Use existing matcher if any.
|
|
|
|
if (typeof pathOrMatcher === 'function') {
|
|
|
|
return pathOrMatcher(segments, segmentGroup, route);
|
|
|
|
}
|
|
|
|
|
|
|
|
const path = pathOrMatcher;
|
2021-01-19 15:28:37 +00:00
|
|
|
const parts = path.split('/');
|
2021-01-26 08:25:54 +00:00
|
|
|
const isFullMatch = route.pathMatch === 'full';
|
|
|
|
const posParams: Record<string, UrlSegment> = {};
|
|
|
|
|
|
|
|
// The path matches anything.
|
|
|
|
if (path === '') {
|
|
|
|
return (!isFullMatch || segments.length === 0) ? { consumed: [] } : null;
|
|
|
|
}
|
2021-01-19 15:28:37 +00:00
|
|
|
|
|
|
|
// The actual URL is shorter than the config, no match.
|
|
|
|
if (parts.length > segments.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The config is longer than the actual URL but we are looking for a full match, return null.
|
|
|
|
if (isFullMatch && (segmentGroup.hasChildren() || parts.length < segments.length)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check each config part against the actual URL.
|
|
|
|
for (let index = 0; index < parts.length; index++) {
|
|
|
|
const part = parts[index];
|
|
|
|
const segment = segments[index];
|
|
|
|
const isParameter = part.startsWith(':');
|
|
|
|
|
|
|
|
if (isParameter) {
|
|
|
|
posParams[part.substring(1)] = segment;
|
|
|
|
} else if (part !== segment.path) {
|
|
|
|
// The actual URL part does not match the config, no match.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return consumed segments with params.
|
|
|
|
return { consumed: segments.slice(0, parts.length), posParams };
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-30 16:25:48 +00:00
|
|
|
export type ModuleRoutes = { children: Routes; siblings: Routes };
|
2020-12-15 08:47:15 +00:00
|
|
|
export type ModuleRoutesConfig = Routes | Partial<ModuleRoutes>;
|
2020-11-30 16:25:48 +00:00
|
|
|
|
2021-01-19 15:28:37 +00:00
|
|
|
/**
|
|
|
|
* Configure routes so that they'll only match when a given condition is met.
|
|
|
|
*
|
|
|
|
* @param routes Routes.
|
|
|
|
* @param condition Condition to determine if routes should be activated or not.
|
|
|
|
* @return Conditional routes.
|
|
|
|
*/
|
|
|
|
export function conditionalRoutes(routes: Routes, condition: () => boolean): Routes {
|
2021-01-26 08:25:54 +00:00
|
|
|
return routes.map(route => {
|
|
|
|
// We need to remove the path from the route because Angular doesn't call the matcher for empty paths.
|
|
|
|
const { path, matcher, ...newRoute } = route;
|
2021-01-19 15:28:37 +00:00
|
|
|
|
2021-01-26 08:25:54 +00:00
|
|
|
return {
|
|
|
|
...newRoute,
|
|
|
|
matcher: buildConditionalUrlMatcher(matcher || path!, condition),
|
|
|
|
};
|
|
|
|
});
|
2021-01-19 15:28:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve module routes.
|
|
|
|
*
|
|
|
|
* @param injector Module injector.
|
|
|
|
* @param token Routes injection token.
|
|
|
|
* @return Routes.
|
|
|
|
*/
|
2020-12-15 08:47:15 +00:00
|
|
|
export function resolveModuleRoutes(injector: Injector, token: InjectionToken<ModuleRoutesConfig[]>): ModuleRoutes {
|
|
|
|
const configs = injector.get(token, []);
|
|
|
|
const routes = configs.map(config => {
|
|
|
|
if (Array.isArray(config)) {
|
|
|
|
return {
|
|
|
|
children: [],
|
|
|
|
siblings: config,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
children: config.children || [],
|
|
|
|
siblings: config.siblings || [],
|
|
|
|
};
|
|
|
|
});
|
2020-11-30 16:25:48 +00:00
|
|
|
|
|
|
|
return {
|
2020-12-15 08:47:15 +00:00
|
|
|
children: CoreArray.flatten(routes.map(r => r.children)),
|
|
|
|
siblings: CoreArray.flatten(routes.map(r => r.siblings)),
|
2020-11-30 16:25:48 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export const APP_ROUTES = new InjectionToken('APP_ROUTES');
|
2020-10-05 12:56:27 +00:00
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
imports: [
|
2020-11-30 16:25:48 +00:00
|
|
|
RouterModule.forRoot([], {
|
2020-10-29 13:32:48 +00:00
|
|
|
preloadingStrategy: PreloadAllModules,
|
|
|
|
relativeLinkResolution: 'corrected',
|
|
|
|
}),
|
2020-10-05 12:56:27 +00:00
|
|
|
],
|
2020-11-30 16:25:48 +00:00
|
|
|
providers: [
|
|
|
|
{ provide: ROUTES, multi: true, useFactory: buildAppRoutes, deps: [Injector] },
|
|
|
|
],
|
2020-10-05 12:56:27 +00:00
|
|
|
exports: [RouterModule],
|
|
|
|
})
|
2020-11-30 16:25:48 +00:00
|
|
|
export class AppRoutingModule {
|
|
|
|
|
|
|
|
static forChild(routes: Routes): ModuleWithProviders<AppRoutingModule> {
|
|
|
|
return {
|
|
|
|
ngModule: AppRoutingModule,
|
|
|
|
providers: [
|
|
|
|
{ provide: APP_ROUTES, multi: true, useValue: routes },
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|