NGXS:如何在 meta-reducer 中使用 Store 或 set State

NGXS: How to use Store or set State in meta-reducer

我需要从 meta-reducer 或插件中发送一个动作。将此提供程序添加到应用程序模块时出现以下错误:

   {
       provide: NGXS_PLUGINS,
       useFactory: myfunction,
       deps: [Store],
       multi: true
    }

Cannot instantiate cyclic dependency! InternalStateOperations ("[ERROR ->]"): in NgModule AppModule

Cannot instantiate cyclic dependency! StateFactory ("[ERROR ->]"): in NgModule AppModule

正确的做法是什么?

元减速器是:

export function extendApplication(store: Store) {
  return function extendApplication(state, action, next) {
  if (state.user.loggedIn){

    if (getActionTypeFromInstance(action) !== LogoutUser.type) {

      here is where I want to set a timer and if no other actions
      occur before the timer expires I want to dispatch a logout action

      return next(state, action);
    }
  }else{
    return next(state, action);
  }}

模块有上述供应商。

MetaReducers 可以通过函数或服务实现 (Class)。

如果你通过一个函数实现它,你可以这样做:

import { NgModule } from '@angular/core';
import { NGXS_PLUGINS } from '@ngxs/store';
import { getActionTypeFromInstance } from '@ngxs/store';

@NgModule({
  imports: [NgxsModule.forRoot([])],
  providers: [
    {
      provide: NGXS_PLUGINS,
      useValue: logoutPlugin,
      multi: true
    }
  ]
})
export class AppModule {}

export function logoutPlugin(state, action, next) {
  // Use the get action type helper to determine the type
  if (getActionTypeFromInstance(action) === Logout.type) {
    // if we are a logout type, lets erase all the state
    state = {};
  }

  // return the next function with the empty state
  return next(state, action);
}

仅通过更新传递给函数的 state 对象并将其传递回返回的 next 函数来改变状态。

您可以在插件中注入商店,使用 Injector 并获取实例,但您不能在插件内部调度一个动作,因为您将创建一个无限循环。

如果你想通过服务实现它,你可以这样做:

import {
  NGXS_PLUGINS,
  NgxsModule,
  ActionType,
  NgxsNextPluginFn,
  NgxsPlugin
} from "@ngxs/store";
import { Injectable, Inject, Injector } from '@angular/core';

@NgModule({
  imports: [
    NgxsModule.forRoot([TestState]),
  ],
  providers: [
    {
      provide: NGXS_PLUGINS,
      useClass: TestInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

@Injectable()
export class TestInterceptor implements NgxsPlugin {

  constructor(
    private _injector: Injector
  ){
  }

  public handle(
    state,
    action: ActionType,
    next: NgxsNextPluginFn
  ): NgxsNextPluginFn {
    const matches: (action: ActionType) => boolean = actionMatcher(action);
    const isInitialAction: boolean = matches(InitState) || matches(UpdateState);  

    // you can validate the action executed matches the one you are hooking into
    // and update state accordingly

    // state represents full state obj, if you need to update a specific state, 
    // you need to use the `name` from the @State definition

    state = { test: ["state mutated in plugin"] };

    // get store instance via Injector 
    const store = this._injector.get<Store>(Store);

    return next(state, action);
  }
}


我还创建了 stackblitz 示例,如果你想查看的话

编辑: 实际上,当您可以使用已经具有中间件的插件时,使用中间件是没有意义的。这样我们所要做的就是创建动作:

@Action(RequestLogout)
async requestLogout(context: StateContext<IAuthStateModel>) {
  context.dispatch(new Navigate(['/login']));
  context.dispatch(new StateResetAll());
}

中间件解决方案:

从技术上讲,提供的答案是不正确的,因为 {} 不会将应用程序状态重置为其默认值。

为了将来实施此功能的人员参考,我提供以下解决方案:

import { ActionType, getActionTypeFromInstance, NgxsNextPluginFn, NgxsPlugin, Store } from '@ngxs/store';
import { Injectable, Injector } from '@angular/core';
import { RequestLogout } from './auth.actions';
import { Navigate } from '@ngxs/router-plugin';
import { StateResetAll } from 'ngxs-reset-plugin';

@Injectable()
export class LogoutMiddleware implements NgxsPlugin {

  constructor(
    private injector: Injector,
  ) {
  }

  public handle(
    state: any,
    action: ActionType,
    next: NgxsNextPluginFn,
  ): NgxsNextPluginFn {
    if (getActionTypeFromInstance(action) === RequestLogout.type) {
      const store = this.injector.get<Store>(Store);
      store.dispatch(new Navigate(['/login']));
      store.dispatch(new StateResetAll());
    }

    return next(state, action);
  }
}

然后在你的应用程序模块中,你必须声明 LogoutMiddleware(有趣的是 meta-reducer 只是 redux 中间件的奇特名称)并导入 NgxsResetPluginModule。

providers: [
    {
      provide: NGXS_PLUGINS,
      useClass: LogoutMiddleware,
      multi: true,
    },
    ...
  ],
  imports: [
    NgxsResetPluginModule.forRoot(),
    ...
  ],