@o3r/routing
Version:
This module helps you to configure an Otter application's routing (POST parameter management, preloading strategy, guard recommendation).
353 lines (342 loc) • 18.2 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, NgModule, makeEnvironmentProviders } from '@angular/core';
import * as i1 from '@ngrx/effects';
import { createEffect, ofType } from '@ngrx/effects';
import { ROUTER_REQUEST, ROUTER_NAVIGATED } from '@ngrx/router-store';
import { filter, map, switchMap } from 'rxjs/operators';
import * as i1$1 from '@ngrx/store';
import { createAction, props, on, createReducer, StoreModule, createFeatureSelector, createSelector } from '@ngrx/store';
import { createEntityAdapter } from '@ngrx/entity';
import { APP_BASE_HREF } from '@angular/common';
import { DEFAULT_BUILD_PROPERTIES } from '@o3r/core';
import * as i1$2 from '@angular/router';
import { NavigationEnd } from '@angular/router';
import { of } from 'rxjs';
/** Entity Actions */
const ACTION_REGISTER_ENTITY = '[RoutingGuard] register an entity';
const ACTION_SET_ENTITY_AS_FAILURE = '[RoutingGuard] set an entity state as FAILURE';
const ACTION_SET_ENTITY_AS_SUCCESS_AND_CLEAR_REASON = '[RoutingGuard] set an entity state as SUCCESS and clear reason';
const ACTION_SET_ENTITY_AS_PENDING = '[RoutingGuard] set an entity state as PENDING';
const ACTION_CLEAR_ENTITIES = '[RoutingGuard] clear entities';
const ACTION_CLEAR_FAILURE_REASON = '[RoutingGuard] clear failure reason for an entity';
const ACTION_SET_ENTITY_AS_FAILURE_WITH_REASON = '[RoutingGuard] set an entity state as FAILURE with a reason';
/**
* Register a new entity in routing guard store
*/
const registerRoutingGuardEntity = createAction(ACTION_REGISTER_ENTITY, props());
/**
* Set an entity in FAILURE status
*/
const setRoutingGuardEntityAsFailure = createAction(ACTION_SET_ENTITY_AS_FAILURE, props());
/**
* Set an entity in SUCCESS status and clear the reason
*/
const setRoutingGuardEntityAsSuccessAndClearReason = createAction(ACTION_SET_ENTITY_AS_SUCCESS_AND_CLEAR_REASON, props());
/**
* Set an entity in PENDING status
*/
const setRoutingGuardEntityAsPending = createAction(ACTION_SET_ENTITY_AS_PENDING, props());
/**
* Clear only the entities, keeps the other attributes in the state
*/
const clearRoutingGuardEntities = createAction(ACTION_CLEAR_ENTITIES);
/**
* Clear only the entities, keeps the other attributes in the state
*/
const clearRoutingGuardEntitiesFailureReason = createAction(ACTION_CLEAR_FAILURE_REASON, props());
/**
* Set entity in FAILURE status with a reason
*/
const setRoutingGuardEntityFailureWithReason = createAction(ACTION_SET_ENTITY_AS_FAILURE_WITH_REASON, props());
/**
* Effect to react on Ngrx router store actions
*/
class NgrxStoreRouterEffect {
constructor(actions$) {
this.actions$ = actions$;
/**
* Clear Router registrations when the active history entry changes (ex : click on back/next button Action)
*/
this.resetRouterRegistrationOnRequest$ = createEffect(() => this.actions$.pipe(ofType(ROUTER_REQUEST), filter((action) => action.payload.event.navigationTrigger === 'popstate'), map(() => clearRoutingGuardEntities())));
/**
* Clear Router registrations when navigation happened
*/
this.resetRouterRegistrationOnNavigated$ = createEffect(() => this.actions$.pipe(ofType(ROUTER_NAVIGATED), map(() => clearRoutingGuardEntities())));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: NgrxStoreRouterEffect, deps: [{ token: i1.Actions }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: NgrxStoreRouterEffect }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: NgrxStoreRouterEffect, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.Actions }] });
/** Identifies the status of a registered block */
var RegisteredItemStatus;
(function (RegisteredItemStatus) {
/** block is ready (async call has not been triggered) */
RegisteredItemStatus["READY"] = "READY";
/** block is waiting for async call to resolve */
RegisteredItemStatus["PENDING"] = "PENDING";
/** block received the async call result and it's a failure */
RegisteredItemStatus["FAILURE"] = "FAILURE";
/** block received the async call result and it's a success */
RegisteredItemStatus["SUCCESS"] = "SUCCESS";
})(RegisteredItemStatus || (RegisteredItemStatus = {}));
/** Identifies the failure reason for the registered block */
var RegisteredItemFailureReason;
(function (RegisteredItemFailureReason) {
/** Routing Failed because the async call result is failure */
RegisteredItemFailureReason["ASYNC_CALL_FAILURE"] = "ASYNC_CALL_FAILURE";
/** Routing failed because the changes to the form has not been saved */
RegisteredItemFailureReason["FORM_UNSAVED"] = "FORM_UNSAVED";
})(RegisteredItemFailureReason || (RegisteredItemFailureReason = {}));
/**
* Name of the RoutingGuard Store
*/
const ROUTING_GUARD_STORE_NAME = 'routingGuard';
/**
* RoutingGuard Store adapter
*/
const routingGuardAdapter = createEntityAdapter({
selectId: (model) => model.id
});
/**
* RoutingGuard Store initial value
*/
const routingGuardInitialState = routingGuardAdapter.getInitialState({});
/**
* List of basic actions for RoutingGuard Store
*/
const routingGuardReducerFeatures = [
on(registerRoutingGuardEntity, (state, payload) => routingGuardAdapter.addOne({ id: payload.id, status: RegisteredItemStatus.READY }, state)),
on(setRoutingGuardEntityAsPending, (state, payload) => routingGuardAdapter.updateOne({ id: payload.id, changes: { status: RegisteredItemStatus.PENDING } }, state)),
on(setRoutingGuardEntityAsFailure, (state, payload) => routingGuardAdapter.updateOne({ id: payload.id, changes: { status: RegisteredItemStatus.FAILURE } }, state)),
on(setRoutingGuardEntityAsSuccessAndClearReason, (state, payload) => routingGuardAdapter.updateOne({ id: payload.id, changes: { status: RegisteredItemStatus.SUCCESS, blockingReason: undefined } }, state)),
on(clearRoutingGuardEntities, (state) => routingGuardAdapter.removeAll(state)),
on(clearRoutingGuardEntitiesFailureReason, (state, payload) => routingGuardAdapter.updateOne({ id: payload.id, changes: { status: RegisteredItemStatus.READY, blockingReason: undefined } }, state)),
on(setRoutingGuardEntityFailureWithReason, (state, payload) => routingGuardAdapter.updateOne({ id: payload.id, changes: { status: RegisteredItemStatus.FAILURE, blockingReason: payload.reason } }, state))
];
/**
* RoutingGuard Store reducer
*/
const routingGuardReducer = createReducer(routingGuardInitialState, ...routingGuardReducerFeatures);
/** Token of the RoutingGuard reducer */
const ROUTING_GUARD_REDUCER_TOKEN = new InjectionToken('Feature RoutingGuard Reducer');
/** Provide default reducer for RoutingGuard store */
function getDefaultRoutingGuardReducer() {
return routingGuardReducer;
}
class RoutingGuardStoreModule {
static forRoot(reducerFactory) {
return {
ngModule: RoutingGuardStoreModule,
providers: [
{ provide: ROUTING_GUARD_REDUCER_TOKEN, useFactory: reducerFactory }
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: RoutingGuardStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.10", ngImport: i0, type: RoutingGuardStoreModule, imports: [i1$1.StoreFeatureModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: RoutingGuardStoreModule, providers: [
{ provide: ROUTING_GUARD_REDUCER_TOKEN, useFactory: getDefaultRoutingGuardReducer }
], imports: [StoreModule.forFeature(ROUTING_GUARD_STORE_NAME, ROUTING_GUARD_REDUCER_TOKEN)] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: RoutingGuardStoreModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule.forFeature(ROUTING_GUARD_STORE_NAME, ROUTING_GUARD_REDUCER_TOKEN)
],
providers: [
{ provide: ROUTING_GUARD_REDUCER_TOKEN, useFactory: getDefaultRoutingGuardReducer }
]
}]
}] });
const { selectIds, selectEntities, selectAll, selectTotal } = routingGuardAdapter.getSelectors();
/** Select RoutingGuard State */
const selectRoutingGuardState = createFeatureSelector(ROUTING_GUARD_STORE_NAME);
/** Select the array of RoutingGuard ids */
const selectRoutingGuardIds = createSelector(selectRoutingGuardState, selectIds);
/** Select the array of RoutingGuard */
const selectAllRoutingGuard = createSelector(selectRoutingGuardState, selectAll);
/** Select the dictionary of RoutingGuard entities */
const selectRoutingGuardEntities = createSelector(selectRoutingGuardState, selectEntities);
/** Select the total RoutingGuard count */
const selectRoutingGuardTotal = createSelector(selectRoutingGuardState, selectTotal);
/**
* Selector used to retrieve the list of entities' status
*/
const selectRoutingGuardEntitiesStatusList = createSelector(selectRoutingGuardState, (state) => {
return Object.keys(state.entities)
.filter((key) => !!state.entities[key])
.map((key) => state.entities[key].status);
});
/**
* Selector used to retrieve the list of entities' blocking reasons for FAILURE status.
*/
const selectRoutingGuardEntitiesBlockingReasons = createSelector(selectRoutingGuardState, (state) => {
return Object.values(state.entities)
.filter((entity) => !!entity && !!entity.blockingReason)
.map((entity) => entity.blockingReason);
});
/**
* Selector used to check that no item is in PENDING state.
* For example, we will rely on this selector to trigger the canDeactivate logic in the RoutingGuard
*/
const hasNoEntitiesInPendingState = createSelector(selectRoutingGuardEntitiesStatusList, (statusList) => {
return !statusList.includes(RegisteredItemStatus.PENDING);
});
/**
* Selector used to check that no item is in FAILURE state.
* For example, we will rely on this selector to authorize or not the navigation in the RoutingGuard
*/
const hasNoEntitiesInFailureState = createSelector(selectRoutingGuardEntitiesStatusList, (statusList) => {
return !statusList.includes(RegisteredItemStatus.FAILURE);
});
/**
* Selector used to check that no item is in READY or FAILURE state.
* For example, we will rely on this selector to trigger the router navigation and display a loader
*/
const hasNoEntityInReadyOrFailureState = createSelector(selectRoutingGuardEntitiesStatusList, (statusList) => {
return !statusList.some((status) => status === RegisteredItemStatus.READY || status === RegisteredItemStatus.FAILURE);
});
/**
* Selector used to check that no item is in FAILURE state with a blocking reason provided.
* Takes `blockingReason: RegisteredItemFailureReason` as aproperty parameter.
*/
const hasNoEntityFailureStateWithReasons = createSelector(selectRoutingGuardEntitiesBlockingReasons, (reasons, properties) => {
return !reasons.includes(properties.blockingReason);
});
/**
* AppBaseHref factory function
* The APP_BASE_HREF will assume one of the following values (ordered by priority):
* 1- undefined (or disabled) if it's not a PROD environment
* 2- the content of the data tag data-appbasehref in the body if it exists
* 3- the content of config.APP_BASE_HREF if defined
* 4- otherwise, undefined
* @param config The application environment configuration
*/
function appBaseHrefFactory(config) {
if (config.ENVIRONMENT !== 'prod') {
return;
}
const dynamicBaseHref = document.body.dataset.appbasehref;
if (typeof dynamicBaseHref !== 'undefined') {
return dynamicBaseHref;
}
return typeof config.APP_BASE_HREF === 'string' ? config.APP_BASE_HREF : undefined;
}
const ENVIRONMENT_CONFIG_TOKEN = new InjectionToken('Environment config');
/**
* @deprecated Will be removed in v14.
*/
class AppServerRoutingModule {
/**
* Injects the APP_BASE_HREF with a custom factory
* @param config The application environment configuration
* @deprecated Please use {@link provideEnvironment} instead. Will be removed in v14.
*/
static forRoot(config) {
return {
ngModule: AppServerRoutingModule,
providers: [
{ provide: ENVIRONMENT_CONFIG_TOKEN, useValue: { ...DEFAULT_BUILD_PROPERTIES, ...config } },
{
provide: APP_BASE_HREF,
useFactory: appBaseHrefFactory,
deps: [ENVIRONMENT_CONFIG_TOKEN]
}
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AppServerRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.10", ngImport: i0, type: AppServerRoutingModule }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AppServerRoutingModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: AppServerRoutingModule, decorators: [{
type: NgModule
}] });
/**
* Specify a custom base href
* @param baseHref
*/
function withBaseHref(baseHref) {
return {
ɵkind: 'base-href',
ɵproviders: [
{
provide: APP_BASE_HREF,
...(typeof baseHref === 'function'
? { useFactory: baseHref, deps: [ENVIRONMENT_CONFIG_TOKEN] }
: { useValue: baseHref })
}
]
};
}
/**
* Provide environment configuration
* @note it will also provide APP_BASE_HREF based on the environment configuration
* @param config
* @param features
*/
function provideEnvironment(config, ...features) {
const additionalProviders = [];
const baseHrefFeature = features.find((f) => f.ɵkind === 'base-href') ?? withBaseHref(appBaseHrefFactory);
additionalProviders.push(baseHrefFeature.ɵproviders);
return makeEnvironmentProviders([
{ provide: ENVIRONMENT_CONFIG_TOKEN, useValue: { ...DEFAULT_BUILD_PROPERTIES, ...config } },
additionalProviders
]);
}
/**
* Check if the route has preload instructions
* @param data Route data
*/
function hasPreloadingOnDemand(data) {
if (!data) {
return false;
}
return (Array.isArray(data.preloadOn) && data.preloadOn.length > 0)
|| data.preloadOn === '*'
|| data.preloadOn instanceof RegExp;
}
/**
* Otter Preloading strategy base on previous route
* @inheritDoc
*/
class O3rOnNavigationPreloadingStrategy {
constructor(router) {
this.router = router;
}
/**
* Check if the module should be preloaded based on the data preload array of routes or regex value
* @param data Route data
* @param url URL of current page
*/
isUrlMatchingPreloadConfig(data, url) {
return (Array.isArray(data.preloadOn) && data.preloadOn.includes(url))
|| (data.preloadOn instanceof RegExp && data.preloadOn.test(url));
}
/** @inheritDoc */
preload(route, preload) {
if ((route.path || route.matcher) && hasPreloadingOnDemand(route.data)) {
// On application landing page, check the route to preload.
if (route.data.preloadOn === '*' || this.isUrlMatchingPreloadConfig(route.data, this.router.routerState.snapshot.url)) {
return preload();
}
// On route navigation end, load the routes to preload for the current route.
return this.router.events
.pipe(filter((r) => r instanceof NavigationEnd && this.isUrlMatchingPreloadConfig(route.data, r.url)), switchMap(() => preload()));
}
return of(null);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: O3rOnNavigationPreloadingStrategy, deps: [{ token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: O3rOnNavigationPreloadingStrategy }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: O3rOnNavigationPreloadingStrategy, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1$2.Router }] });
/**
* Generated bundle index. Do not edit.
*/
export { AppServerRoutingModule, ENVIRONMENT_CONFIG_TOKEN, NgrxStoreRouterEffect, O3rOnNavigationPreloadingStrategy, ROUTING_GUARD_REDUCER_TOKEN, ROUTING_GUARD_STORE_NAME, RegisteredItemFailureReason, RegisteredItemStatus, RoutingGuardStoreModule, appBaseHrefFactory, clearRoutingGuardEntities, clearRoutingGuardEntitiesFailureReason, getDefaultRoutingGuardReducer, hasNoEntitiesInFailureState, hasNoEntitiesInPendingState, hasNoEntityFailureStateWithReasons, hasNoEntityInReadyOrFailureState, hasPreloadingOnDemand, provideEnvironment, registerRoutingGuardEntity, routingGuardAdapter, routingGuardInitialState, routingGuardReducer, routingGuardReducerFeatures, selectAllRoutingGuard, selectRoutingGuardEntities, selectRoutingGuardEntitiesBlockingReasons, selectRoutingGuardEntitiesStatusList, selectRoutingGuardIds, selectRoutingGuardState, selectRoutingGuardTotal, setRoutingGuardEntityAsFailure, setRoutingGuardEntityAsPending, setRoutingGuardEntityAsSuccessAndClearReason, setRoutingGuardEntityFailureWithReason, withBaseHref };
//# sourceMappingURL=o3r-routing.mjs.map