I need to dispatch an action from within a meta-reducer or a plugin. I get the following errors when I add this provider to the App Module:
{
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
What is the proper way to do it?
The meta-reducer is:
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);
}}
The module has the above provider.
MetaReducers can be implemented via a function or a Service (Class).
If you implement it via a function, you can do:
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 is mutated just by updating the state
object passed into the function and passing it back to the returned next
function.
You can inject the Store in the plugin, using Injector
and getting the instance, but you can't dispatch an actiom inside the plugin, because you'll create an infinite loop.
If you want to implement it via a Service, you can do:
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);
}
}
I also created stackblitz example if you'd like to check it out