From e4fadcbb04536813d44d013b94ec5e579a720914 Mon Sep 17 00:00:00 2001 From: arturovt Date: Wed, 13 Nov 2024 18:22:16 +0200 Subject: [PATCH] fix(store): allow plain functions in `withNgxsPlugin` In this commit, we slightly revise the `PluginManager` functionality to a single function that pushes values onto the `plugins` list. No additional functionality is needed, and we mark it as a root provider. We also allow passing a plain function into `withNgxsPlugin`, as the plugin can be a simple function (not a class). --- docs/concepts/store/meta-reducer.md | 14 ++----- .../devtools-plugin/src/devtools.module.ts | 7 +--- packages/form-plugin/src/form.module.ts | 18 ++------- packages/logger-plugin/src/logger.module.ts | 7 +--- packages/storage-plugin/src/storage.module.ts | 7 +--- packages/store/plugins/src/index.ts | 8 +++- packages/store/plugins/src/symbols.ts | 27 ++++++++++--- packages/store/src/plugin-manager.ts | 40 +++++-------------- .../standalone-features/feature-providers.ts | 2 - .../store/src/standalone-features/plugin.ts | 14 +++++-- .../src/standalone-features/root-providers.ts | 2 - packages/store/tests/plugins.spec.ts | 10 +---- 12 files changed, 59 insertions(+), 97 deletions(-) diff --git a/docs/concepts/store/meta-reducer.md b/docs/concepts/store/meta-reducer.md index ee420ba88..e0c5b4ee8 100644 --- a/docs/concepts/store/meta-reducer.md +++ b/docs/concepts/store/meta-reducer.md @@ -19,21 +19,13 @@ export function logoutPlugin(state, action, next) { } ``` -Then add it to `providers`: +Then add it to `provideStore` features: ```ts -import { NGXS_PLUGINS } from '@ngxs/store/plugins'; +import { provideStore, withNgxsPlugin } from '@ngxs/store'; export const appConfig: ApplicationConfig = { - providers: [ - provideStore([]), - - { - provide: NGXS_PLUGINS, - useValue: logoutPlugin, - multi: true - } - ] + providers: [provideStore([], withNgxsPlugin(logoutPlugin))] }; ``` diff --git a/packages/devtools-plugin/src/devtools.module.ts b/packages/devtools-plugin/src/devtools.module.ts index 4239299a8..929a2b6fa 100644 --- a/packages/devtools-plugin/src/devtools.module.ts +++ b/packages/devtools-plugin/src/devtools.module.ts @@ -6,7 +6,6 @@ import { makeEnvironmentProviders } from '@angular/core'; import { withNgxsPlugin } from '@ngxs/store'; -import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsDevtoolsOptions, NGXS_DEVTOOLS_OPTIONS } from './symbols'; import { NgxsReduxDevtoolsPlugin } from './devtools.plugin'; @@ -28,11 +27,7 @@ export class NgxsReduxDevtoolsPluginModule { return { ngModule: NgxsReduxDevtoolsPluginModule, providers: [ - { - provide: NGXS_PLUGINS, - useClass: NgxsReduxDevtoolsPlugin, - multi: true - }, + withNgxsPlugin(NgxsReduxDevtoolsPlugin), { provide: USER_OPTIONS, useValue: options diff --git a/packages/form-plugin/src/form.module.ts b/packages/form-plugin/src/form.module.ts index eb0fcae1f..a06f9c035 100644 --- a/packages/form-plugin/src/form.module.ts +++ b/packages/form-plugin/src/form.module.ts @@ -1,11 +1,5 @@ -import { - NgModule, - ModuleWithProviders, - EnvironmentProviders, - makeEnvironmentProviders -} from '@angular/core'; +import { NgModule, ModuleWithProviders, EnvironmentProviders } from '@angular/core'; import { withNgxsPlugin } from '@ngxs/store'; -import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsFormPlugin } from './form.plugin'; import { NgxsFormDirective } from './directive'; @@ -18,17 +12,11 @@ export class NgxsFormPluginModule { static forRoot(): ModuleWithProviders { return { ngModule: NgxsFormPluginModule, - providers: [ - { - provide: NGXS_PLUGINS, - useClass: NgxsFormPlugin, - multi: true - } - ] + providers: [withNgxsPlugin(NgxsFormPlugin)] }; } } export function withNgxsFormPlugin(): EnvironmentProviders { - return makeEnvironmentProviders([withNgxsPlugin(NgxsFormPlugin)]); + return withNgxsPlugin(NgxsFormPlugin); } diff --git a/packages/logger-plugin/src/logger.module.ts b/packages/logger-plugin/src/logger.module.ts index bb4be3b1e..8432d1d0a 100644 --- a/packages/logger-plugin/src/logger.module.ts +++ b/packages/logger-plugin/src/logger.module.ts @@ -6,7 +6,6 @@ import { makeEnvironmentProviders } from '@angular/core'; import { withNgxsPlugin } from '@ngxs/store'; -import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { NgxsLoggerPlugin } from './logger.plugin'; import { NgxsLoggerPluginOptions, NGXS_LOGGER_PLUGIN_OPTIONS } from './symbols'; @@ -35,11 +34,7 @@ export class NgxsLoggerPluginModule { return { ngModule: NgxsLoggerPluginModule, providers: [ - { - provide: NGXS_PLUGINS, - useClass: NgxsLoggerPlugin, - multi: true - }, + withNgxsPlugin(NgxsLoggerPlugin), { provide: USER_OPTIONS, useValue: options diff --git a/packages/storage-plugin/src/storage.module.ts b/packages/storage-plugin/src/storage.module.ts index 1b7da5ab2..98fe4b06e 100644 --- a/packages/storage-plugin/src/storage.module.ts +++ b/packages/storage-plugin/src/storage.module.ts @@ -6,7 +6,6 @@ import { makeEnvironmentProviders } from '@angular/core'; import { withNgxsPlugin } from '@ngxs/store'; -import { NGXS_PLUGINS } from '@ngxs/store/plugins'; import { ɵUSER_OPTIONS, STORAGE_ENGINE, @@ -25,11 +24,7 @@ export class NgxsStoragePluginModule { return { ngModule: NgxsStoragePluginModule, providers: [ - { - provide: NGXS_PLUGINS, - useClass: NgxsStoragePlugin, - multi: true - }, + withNgxsPlugin(NgxsStoragePlugin), { provide: ɵUSER_OPTIONS, useValue: options diff --git a/packages/store/plugins/src/index.ts b/packages/store/plugins/src/index.ts index 1596c2996..869731d05 100644 --- a/packages/store/plugins/src/index.ts +++ b/packages/store/plugins/src/index.ts @@ -1,3 +1,9 @@ export { InitState, UpdateState } from './actions'; -export { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn, NgxsNextPluginFn } from './symbols'; +export { + NGXS_PLUGINS, + NgxsPlugin, + NgxsPluginFn, + NgxsNextPluginFn, + ɵisPluginClass +} from './symbols'; export { getActionTypeFromInstance, actionMatcher, setValue, getValue } from './utils'; diff --git a/packages/store/plugins/src/symbols.ts b/packages/store/plugins/src/symbols.ts index 6dea12564..b7c85cdeb 100644 --- a/packages/store/plugins/src/symbols.ts +++ b/packages/store/plugins/src/symbols.ts @@ -1,17 +1,13 @@ -import { InjectionToken } from '@angular/core'; +import { InjectionToken, Type } from '@angular/core'; declare const ngDevMode: boolean; const NG_DEV_MODE = typeof ngDevMode !== 'undefined' && ngDevMode; -// The injection token is used to resolve to custom NGXS plugins provided -// at the root level through either `{provide}` scheme or `withNgxsPlugin`. -export const NGXS_PLUGINS = new InjectionToken(NG_DEV_MODE ? 'NGXS_PLUGINS' : ''); +export type NgxsNextPluginFn = (state: any, mutation: any) => any; export type NgxsPluginFn = (state: any, mutation: any, next: NgxsNextPluginFn) => any; -export type NgxsNextPluginFn = (state: any, mutation: any) => any; - /** * Plugin interface */ @@ -21,3 +17,22 @@ export interface NgxsPlugin { */ handle(state: any, action: any, next: NgxsNextPluginFn): any; } + +/** + * A multi-provider token used to resolve to custom NGXS plugins provided + * at the root and feature levels through the `{provide}` scheme. + * + * @deprecated from v18.0.0, use `withNgxsPlugin` instead. + */ +export const NGXS_PLUGINS = /* @__PURE__ */ new InjectionToken( + NG_DEV_MODE ? 'NGXS_PLUGINS' : '' +); + +export function ɵisPluginClass( + plugin: Type | NgxsPluginFn +): plugin is Type { + // Determines whether the provided value is a class rather than a function. + // If it’s a class, its handle method should be defined on its prototype, + // as plugins can be either classes or functions. + return !!plugin.prototype.handle; +} diff --git a/packages/store/src/plugin-manager.ts b/packages/store/src/plugin-manager.ts index 20e40dd3b..d9f9c323e 100644 --- a/packages/store/src/plugin-manager.ts +++ b/packages/store/src/plugin-manager.ts @@ -1,37 +1,17 @@ -import { inject, Injectable } from '@angular/core'; -import { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn } from '@ngxs/store/plugins'; +import { Injectable } from '@angular/core'; +import { NgxsPlugin, NgxsPluginFn } from '@ngxs/store/plugins'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class PluginManager { readonly plugins: NgxsPluginFn[] = []; - private readonly _parentManager = inject(PluginManager, { - optional: true, - skipSelf: true - }); - - private readonly _pluginHandlers = inject(NGXS_PLUGINS, { - optional: true - }); - - constructor() { - this.registerHandlers(); - } - - private get _rootPlugins(): NgxsPluginFn[] { - return this._parentManager?.plugins || this.plugins; - } - - private registerHandlers(): void { - const pluginHandlers: NgxsPluginFn[] = this.getPluginHandlers(); - this._rootPlugins.push(...pluginHandlers); - } - - private getPluginHandlers(): NgxsPluginFn[] { - const handlers: NgxsPlugin[] = this._pluginHandlers || []; - return handlers.map( - (plugin: NgxsPlugin) => - (plugin.handle ? plugin.handle.bind(plugin) : plugin) as NgxsPluginFn + /** @internal */ + registerPlugins(plugins: NgxsPlugin[]): void { + this.plugins.push( + ...plugins.map( + (plugin: NgxsPlugin) => + (plugin.handle ? plugin.handle.bind(plugin) : plugin) as NgxsPluginFn + ) ); } } diff --git a/packages/store/src/standalone-features/feature-providers.ts b/packages/store/src/standalone-features/feature-providers.ts index c115bfaa9..7ca1662c3 100644 --- a/packages/store/src/standalone-features/feature-providers.ts +++ b/packages/store/src/standalone-features/feature-providers.ts @@ -2,7 +2,6 @@ import { Provider } from '@angular/core'; import { ɵStateClass } from '@ngxs/store/internals'; import { FEATURE_STATE_TOKEN } from '../symbols'; -import { PluginManager } from '../plugin-manager'; import { StateFactory } from '../internal/state-factory'; /** @@ -12,7 +11,6 @@ import { StateFactory } from '../internal/state-factory'; export function getFeatureProviders(states: ɵStateClass[]): Provider[] { return [ StateFactory, - PluginManager, ...states, { provide: FEATURE_STATE_TOKEN, diff --git a/packages/store/src/standalone-features/plugin.ts b/packages/store/src/standalone-features/plugin.ts index fe1672758..75d43b4b1 100644 --- a/packages/store/src/standalone-features/plugin.ts +++ b/packages/store/src/standalone-features/plugin.ts @@ -5,7 +5,7 @@ import { inject, makeEnvironmentProviders } from '@angular/core'; -import { NGXS_PLUGINS, NgxsPlugin } from '@ngxs/store/plugins'; +import { NGXS_PLUGINS, NgxsPlugin, NgxsPluginFn, ɵisPluginClass } from '@ngxs/store/plugins'; import { PluginManager } from '../plugin-manager'; @@ -23,12 +23,18 @@ import { PluginManager } from '../plugin-manager'; * }); * ``` */ -export function withNgxsPlugin(plugin: Type): EnvironmentProviders { +export function withNgxsPlugin(plugin: Type | NgxsPluginFn): EnvironmentProviders { return makeEnvironmentProviders([ - { provide: NGXS_PLUGINS, useClass: plugin, multi: true }, + ɵisPluginClass(plugin) + ? { provide: NGXS_PLUGINS, useClass: plugin, multi: true } + : { provide: NGXS_PLUGINS, useValue: plugin, multi: true }, // We should inject the `PluginManager` to retrieve `NGXS_PLUGINS` and // register those plugins. The plugin can be added from inside the child // route, so the plugin manager should be re-injected. - { provide: ENVIRONMENT_INITIALIZER, useValue: () => inject(PluginManager), multi: true } + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(PluginManager).registerPlugins(inject(NGXS_PLUGINS)), + multi: true + } ]); } diff --git a/packages/store/src/standalone-features/root-providers.ts b/packages/store/src/standalone-features/root-providers.ts index a734dd81e..73c50e530 100644 --- a/packages/store/src/standalone-features/root-providers.ts +++ b/packages/store/src/standalone-features/root-providers.ts @@ -1,7 +1,6 @@ import { APP_BOOTSTRAP_LISTENER, Provider, inject } from '@angular/core'; import { ɵStateClass, ɵNgxsAppBootstrappedState } from '@ngxs/store/internals'; -import { PluginManager } from '../plugin-manager'; import { StateFactory } from '../internal/state-factory'; import { CUSTOM_NGXS_EXECUTION_STRATEGY } from '../execution/symbols'; import { NgxsModuleOptions, ROOT_STATE_TOKEN, NGXS_OPTIONS } from '../symbols'; @@ -16,7 +15,6 @@ export function getRootProviders( ): Provider[] { return [ StateFactory, - PluginManager, ...states, { provide: ROOT_STATE_TOKEN, diff --git a/packages/store/tests/plugins.spec.ts b/packages/store/tests/plugins.spec.ts index 4655b4d7c..7473ca72a 100644 --- a/packages/store/tests/plugins.spec.ts +++ b/packages/store/tests/plugins.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { NgxsModule, NGXS_PLUGINS, Store } from '@ngxs/store'; +import { NgxsModule, Store, withNgxsPlugin } from '@ngxs/store'; import { tap } from 'rxjs/operators'; import { Observable } from 'rxjs'; @@ -31,13 +31,7 @@ describe('Plugins', () => { TestBed.configureTestingModule({ imports: [NgxsModule.forRoot()], - providers: [ - { - provide: NGXS_PLUGINS, - useValue: logPlugin, - multi: true - } - ] + providers: [withNgxsPlugin(logPlugin)] }); const store: Store = TestBed.inject(Store);