I use a custom middleware (meta-reducer) to print my ngrx-store
each time an action is dipatched. I wrote my middleware directly inside app.module.ts
(where else should I put it ?):
app.module.ts
// ...imports
// ...
// FIXME:
/**
* console.log action and state(before action) each time an action is dipatched
* @param reducer reducer
*/
export function debug(reducer: ActionReducer<AppState, Actions>): ActionReducer<AppState, Actions> {
const logger = new LoggerService(); ////////////// ERROR \\\\\\\\\\\\\\
return (state, action) => {
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
return reducer(state, action);
};
}
export const metaReducers: MetaReducer<any>[] = [
debug,
];
@NgModule({
declarations: [AppComponent, LoggerServiceComponent],
entryComponents: [],
imports: [
// ...
StoreModule.forRoot(reducers, { metaReducers }),
StoreRouterConnectingModule.forRoot(), // Connects RouterModule with StoreModule
],
providers: [
// ...
],
bootstrap: [AppComponent],
})
export class AppModule {}
There is an error because my LoggerService
has a store injected (because I want all my logs to be stored in ngx-store
. But I can't access the store neither !. Furthermore, I'm sure that's not the good way of accessing the singleton instance of a service...
SomeClass.getServiceInstance(type)
?META_REDUCERS
(1st try - failing)app.module.ts
import { LoggerService } from './services/logger.service';
import { AppState, Actions } from './app.state';
import { StoreModule, MetaReducer, ActionReducer, META_REDUCERS } from '@ngrx/store';
/**
* Injects a `LoggerService` inside a `MetaReducer`
* @param logger a service that allows to log and store console.log() messages
* @returns a `MetaReducer`
*/
function debugFactory(logger: LoggerService): MetaReducer<AppState> {
return (reducer: ActionReducer<AppState, Actions>): ActionReducer<AppState, Actions> => {
return (state, action) => {
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
return reducer(state, action);
};
};
}
/**
* Injects a LoggerService inside the debug `MetaReducer` function
* @param logger a service that allows to log and store console.log() messages
* @returns A list of `MetaReducer`
*/
export function getMetaReducers(logger: LoggerService): MetaReducer<AppState>[] {
return [debugFactory(logger)];
}
const reducers = {
layout: layoutReducer,
preferences: preferencesReducer,
router: routerReducer,
debug: debugReducer,
};
@NgModule({
declarations: [AppComponent ],
entryComponents: [],
imports: [
// ...
StoreModule.forRoot(reducers),
StoreRouterConnectingModule.forRoot(), // Connects RouterModule with StoreModule
],
providers: [
// ...
{
provide: META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
This should work, according to the document but I have the following error at runtime:
TypeError: "fn is not a function"
Corresponding to this function (in the njrx-store library):
/**
* @param {...?} functions
* @return {?}
*/
function compose(...functions) {
return (/**
* @param {?} arg
* @return {?}
*/
function (arg) {
if (functions.length === 0) {
return arg;
}
/** @type {?} */
const last = functions[functions.length - 1];
/** @type {?} */
const rest = functions.slice(0, -1);
return rest.reduceRight((/**
* @param {?} composed
* @param {?} fn
* @return {?}
*/
(composed, fn) => {
return fn(composed) // <----- HERE
}), last(arg));
});
}
In the debugger, it shows that the function array (...functions
) contains some functions and one array, which I suspect is the result of the method getMetaReducers
. I suspect either the example is wrong or there is a problem with the implementation of the compose
method.
Tell me if you see any wrong things in my code.
USER_PROVIDED_META_REDUCERS
as mentioned in answer (2nd try - failing)code that have been edited
// OLD
providers: [
// ...
{
provide: META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
multi: true,
},
],
// NEW
providers: [
// ...
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
},
],
It seems that my LoggerService
is either not correctly passed nor initialized because I have now this error:
core.js:9110 ERROR TypeError: Cannot read property 'storeInfo' of undefined
at http://localhost:8102/main.js:636:20
at http://localhost:8102/vendor.js:109798:20
at computeNextEntry (http://localhost:8102/vendor.js:108628:21)
at recomputeStates (http://localhost:8102/vendor.js:108681:15)
at http://localhost:8102/vendor.js:109029:26
at ScanSubscriber.StoreDevtools.liftedAction$.pipe.Object.state [as accumulator] (http://localhost:8102/vendor.js:109081:38)
at ScanSubscriber._tryNext (http://localhost:8102/vendor.js:120261:27)
at ScanSubscriber._next (http://localhost:8102/vendor.js:120254:25)
at ScanSubscriber.next (http://localhost:8102/vendor.js:114391:18)
at WithLatestFromSubscriber._next (http://localhost:8102/vendor.js:122330:34)
if I comment those lines:
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
No exception will be thrown, but my logger won't work either.
But at least the store configures itself correcly, the problem now is just that the LoggerService is either not correctly passed nor initialized. I guess I'm still doing something wrong
You should try by injecting meta-reducers using the META_REDUCERS
token as documented:
(HEADS UP: as of 24.Sept the docs are a bit misleading, the factory method return type should be MetaReducer
and not MetaReducer[]
. Check this example in the codebase)
export debugFactory(logger: LoggerService): MetaReducer<AppState> {
return (reducer: ActionReducer<AppState, Actions>): ActionReducer<AppState, Actions> => {
return (state, action) => {
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
return reducer(state, action);
};
}
}
@NgModule({
providers: [
{
provide: META_REDUCERS,
deps: [LoggerService],
useFactory: debugFactory,
multi: true
},
],
})
export class AppModule {}
Update:
Since v8 you can use the ÙSER_PROVIDED_META_REDUCERS
token:
export function getMetaReducers(logger: LoggerService): MetaReducer<AppState>[] {
return [debugFactory(logger)];
}
providers: [
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers
},
],
In this case the provided factory method has to return MetaReducer[]
. Both "library" and "user" provided meta-reducers are combined into one collection in this function.
You will most likely hit a cyclic dependency between your store and logger service, as during the creation of the store, angular´s IOC container will try to instantiate a logger service, which depends on the store, before the creation of the store is finalized. You could solve this by "lazily" accessing the store in the logger service by injecting the Injector into it and defining a getter property for the store, something like:
private get() store{return this.injector.get(Store);}
ctor(private readonly injector: Injector){}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With