UNPKG

@ngxs/router-plugin

Version:

router plugin for @ngxs/store

419 lines (411 loc) 15.5 kB
import * as i0 from '@angular/core'; import { inject, NgZone, DestroyRef, Injectable, NgModule, makeEnvironmentProviders } from '@angular/core'; import * as i1 from '@ngxs/store'; import { StateToken, Store, createSelector, Action, State, NgxsModule, provideStates } from '@ngxs/store'; import { ɵNGXS_ROUTER_PLUGIN_OPTIONS as _NGXS_ROUTER_PLUGIN_OPTIONS, NavigationActionTiming, ɵUSER_OPTIONS as _USER_OPTIONS, ɵcreateRouterPluginOptions as _createRouterPluginOptions } from '@ngxs/router-plugin/internals'; export { NavigationActionTiming } from '@ngxs/router-plugin/internals'; import { __decorate } from 'tslib'; import { Router, NavigationStart, RoutesRecognized, ResolveEnd, NavigationCancel, NavigationError, NavigationEnd } from '@angular/router'; /** * Public event api of the router */ class Navigate { path; queryParams; extras; static type = '[Router] Navigate'; constructor(path, queryParams, extras) { this.path = path; this.queryParams = queryParams; this.extras = extras; } } /** * * Angular Routers internal state events * */ /** * An action dispatched when the router starts the navigation. */ class RouterRequest { routerState; event; trigger; static type = '[Router] RouterRequest'; constructor(routerState, event, trigger = 'none') { this.routerState = routerState; this.event = event; this.trigger = trigger; } } /** * An action dispatched when the router navigates. */ class RouterNavigation { routerState; event; trigger; static type = '[Router] RouterNavigation'; constructor(routerState, event, trigger = 'none') { this.routerState = routerState; this.event = event; this.trigger = trigger; } } /** * An action dispatched when the router cancel navigation. */ class RouterCancel { routerState; storeState; event; trigger; static type = '[Router] RouterCancel'; constructor(routerState, storeState, event, trigger = 'none') { this.routerState = routerState; this.storeState = storeState; this.event = event; this.trigger = trigger; } } /** * An action dispatched when the router errors. */ class RouterError { routerState; storeState; event; trigger; static type = '[Router] RouterError'; constructor(routerState, storeState, event, trigger = 'none') { this.routerState = routerState; this.storeState = storeState; this.event = event; this.trigger = trigger; } } /** * An action dispatched when the `ResolveEnd` event is triggered. */ class RouterDataResolved { routerState; event; trigger; static type = '[Router] RouterDataResolved'; constructor(routerState, event, trigger = 'none') { this.routerState = routerState; this.event = event; this.trigger = trigger; } } /** * An action dispatched when the router navigation has been finished successfully. */ class RouterNavigated { routerState; event; trigger; static type = '[Router] RouterNavigated'; constructor(routerState, event, trigger = 'none') { this.routerState = routerState; this.event = event; this.trigger = trigger; } } class RouterStateSerializer { } class DefaultRouterStateSerializer { serialize(routerState) { return { root: this.serializeRoute(routerState.root), url: routerState.url }; } serializeRoute(route) { const children = route.children.map(c => this.serializeRoute(c)); return { url: route.url, title: route.title, params: route.params, queryParams: route.queryParams, fragment: route.fragment, data: route.data, outlet: route.outlet, component: null, routeConfig: null, root: null, parent: null, firstChild: children[0], children: children, pathFromRoot: null, paramMap: route.paramMap, queryParamMap: route.queryParamMap, toString: route.toString }; } } // NGXS doesn't permit untyped selectors, such as `select(RouterState)`, // as the `RouterState` class itself lacks type information. Therefore, // the following state token must replace `RouterState`. const ROUTER_STATE_TOKEN = new StateToken('router'); let RouterState = class RouterState { _store = inject(Store); _router = inject(Router); _serializer = inject(RouterStateSerializer); _ngZone = inject(NgZone); /** * Determines how navigation was performed by the `RouterState` itself * or outside via `new Navigate(...)` */ _trigger = 'none'; /** * That's the serialized state from the `Router` class */ _routerState = null; /** * That's the value of the `RouterState` state */ _storeState = null; _lastEvent = null; _options = inject(_NGXS_ROUTER_PLUGIN_OPTIONS); _subscription; static state() { return createSelector([ROUTER_STATE_TOKEN], (state) => { // The `state` is optional if the selector is invoked before the router // state is registered in NGXS. return state?.state; }); } static url = createSelector([ROUTER_STATE_TOKEN], state => state?.state?.url); constructor() { this._setUpStoreListener(); this._setUpRouterEventsListener(); inject(DestroyRef).onDestroy(() => this._subscription.unsubscribe()); } navigate(_, action) { return this._ngZone.run(() => this._router.navigate(action.path, { queryParams: action.queryParams, ...action.extras })); } /** * Handles all Angular Router actions (request, navigation, cancel, error, data resolved, navigated). * * Each time one of these actions is dispatched, the router state in the NGXS store * is updated to reflect the latest router snapshot and navigation metadata. * * Specifically: * - `trigger`: source of the navigation * - `state`: current `RouterStateSnapshot` of the Angular Router * - `navigationId`: unique ID of the router event * * This ensures the NGXS store always mirrors the latest Angular Router state. */ angularRouterAction(ctx, action) { const state = ctx.getState(); const newState = { trigger: action.trigger, state: action.routerState, navigationId: action.event.id }; // Skip updating NGXS state if nothing has changed. // This avoids updating the internal NGXS state signal, which would otherwise // trigger recomputation of `selectSignal` selectors and schedule unnecessary // Angular change detection cycles. if (state.trigger === newState.trigger && state.navigationId === newState.navigationId) { let equal = false; try { equal = JSON.stringify(this._serializer.serialize(state.state)) === JSON.stringify(this._serializer.serialize(newState.state)); } catch { // If serialization or stringify fails, assume not equal. equal = false; } if (equal) { return; } } ctx.setState(newState); } _setUpStoreListener() { const routerState$ = this._store.select(ROUTER_STATE_TOKEN); routerState$.subscribe((state) => { this._navigateIfNeeded(state); }); } _navigateIfNeeded(routerState) { if (routerState?.trigger === 'devtools') { this._storeState = this._store.selectSnapshot(ROUTER_STATE_TOKEN); } const canSkipNavigation = !this._storeState || !this._storeState.state || !routerState || routerState.trigger === 'router' || this._router.url === this._storeState.state.url || this._lastEvent instanceof NavigationStart; if (canSkipNavigation) { return; } this._storeState = this._store.selectSnapshot(ROUTER_STATE_TOKEN); this._trigger = 'store'; this._ngZone.run(() => this._router.navigateByUrl(this._storeState.state.url)); } _setUpRouterEventsListener() { const dispatchRouterNavigationLate = this._options != null && this._options.navigationActionTiming === NavigationActionTiming.PostActivation; let lastRoutesRecognized; this._subscription = this._router.events.subscribe(event => { this._lastEvent = event; if (event instanceof NavigationStart) { this._navigationStart(event); } else if (event instanceof RoutesRecognized) { lastRoutesRecognized = event; if (!dispatchRouterNavigationLate && this._trigger !== 'store') { this._dispatchRouterNavigation(lastRoutesRecognized); } } else if (event instanceof ResolveEnd) { this._dispatchRouterDataResolved(event); } else if (event instanceof NavigationCancel) { this._dispatchRouterCancel(event); this._reset(); } else if (event instanceof NavigationError) { this._dispatchRouterError(event); this._reset(); } else if (event instanceof NavigationEnd) { if (this._trigger !== 'store') { if (dispatchRouterNavigationLate) { this._dispatchRouterNavigation(lastRoutesRecognized); } this._dispatchRouterNavigated(event); } this._reset(); } }); } /** Reacts to `NavigationStart`. */ _navigationStart(event) { this._routerState = this._serializer.serialize(this._router.routerState.snapshot); if (this._trigger !== 'none') { this._storeState = this._store.selectSnapshot(ROUTER_STATE_TOKEN); this._dispatchRouterAction(new RouterRequest(this._routerState, event, this._trigger)); } } /** Reacts to `ResolveEnd`. */ _dispatchRouterDataResolved(event) { const routerState = this._serializer.serialize(event.state); this._dispatchRouterAction(new RouterDataResolved(routerState, event, this._trigger)); } /** Reacts to `RoutesRecognized` or `NavigationEnd`, depends on the `navigationActionTiming`. */ _dispatchRouterNavigation(lastRoutesRecognized) { const nextRouterState = this._serializer.serialize(lastRoutesRecognized.state); this._dispatchRouterAction(new RouterNavigation(nextRouterState, new RoutesRecognized(lastRoutesRecognized.id, lastRoutesRecognized.url, lastRoutesRecognized.urlAfterRedirects, nextRouterState), this._trigger)); } /** Reacts to `NavigationCancel`. */ _dispatchRouterCancel(event) { this._dispatchRouterAction(new RouterCancel(this._routerState, this._storeState, event, this._trigger)); } /** Reacts to `NavigationEnd`. */ _dispatchRouterError(event) { this._dispatchRouterAction(new RouterError(this._routerState, this._storeState, new NavigationError(event.id, event.url, `${event}`), this._trigger)); } /** Reacts to `NavigationEnd`. */ _dispatchRouterNavigated(event) { const routerState = this._serializer.serialize(this._router.routerState.snapshot); this._dispatchRouterAction(new RouterNavigated(routerState, event, this._trigger)); } _dispatchRouterAction(action) { this._trigger = 'router'; try { this._store.dispatch(action); } finally { this._trigger = 'none'; } } _reset() { this._trigger = 'none'; this._storeState = null; this._routerState = null; } /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RouterState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RouterState }); }; __decorate([ Action(Navigate) ], RouterState.prototype, "navigate", null); __decorate([ Action([ (RouterRequest), (RouterNavigation), (RouterError), (RouterCancel), (RouterDataResolved), (RouterNavigated) ]) ], RouterState.prototype, "angularRouterAction", null); RouterState = __decorate([ State({ name: ROUTER_STATE_TOKEN, defaults: { state: undefined, navigationId: undefined, trigger: 'none' } }) ], RouterState); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RouterState, decorators: [{ type: Injectable }], ctorParameters: () => [], propDecorators: { navigate: [], angularRouterAction: [] } }); class NgxsRouterPluginModule { static forRoot(options) { return { ngModule: NgxsRouterPluginModule, providers: [ { provide: _USER_OPTIONS, useValue: options }, { provide: _NGXS_ROUTER_PLUGIN_OPTIONS, useFactory: _createRouterPluginOptions, deps: [_USER_OPTIONS] }, { provide: RouterStateSerializer, useClass: DefaultRouterStateSerializer } ] }; } /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsRouterPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); /** @nocollapse */ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.2", ngImport: i0, type: NgxsRouterPluginModule, imports: [i1.ɵNgxsFeatureModule] }); /** @nocollapse */ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsRouterPluginModule, imports: [NgxsModule.forFeature([RouterState])] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsRouterPluginModule, decorators: [{ type: NgModule, args: [{ imports: [NgxsModule.forFeature([RouterState])] }] }] }); function withNgxsRouterPlugin(options) { return makeEnvironmentProviders([ provideStates([RouterState]), { provide: _USER_OPTIONS, useValue: options }, { provide: _NGXS_ROUTER_PLUGIN_OPTIONS, useFactory: _createRouterPluginOptions, deps: [_USER_OPTIONS] }, { provide: RouterStateSerializer, useClass: DefaultRouterStateSerializer } ]); } /** * The public api for consumers of @ngxs/router-plugin */ /** * Generated bundle index. Do not edit. */ export { DefaultRouterStateSerializer, Navigate, NgxsRouterPluginModule, ROUTER_STATE_TOKEN, RouterCancel, RouterDataResolved, RouterError, RouterNavigated, RouterNavigation, RouterRequest, RouterState, RouterStateSerializer, withNgxsRouterPlugin }; //# sourceMappingURL=ngxs-router-plugin.mjs.map