From eca4f376c0b8bd864271189df58e8ec8d3a6fc18 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). --- CHANGELOG.md | 2 +- 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 ++++++++++++++----- .../store/src/standalone-features/plugin.ts | 14 +++++++--- packages/store/tests/plugins.spec.ts | 10 ++----- 10 files changed, 50 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3cbe8fa..ad267c63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ $ npm install @ngxs/store@dev ### To become next patch version -- ... +- Fix(store): Allow plain functions in `withNgxsPlugin` [#2255](https://github.com/ngxs/store/pull/2255) ### 18.1.5 2024-11-12 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/standalone-features/plugin.ts b/packages/store/src/standalone-features/plugin.ts index fe1672758..4aa4268ad 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), + multi: true + } ]); } 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);