NgRx:如何在 meta-reducer 中使用服务?
NgRx: How to use services in meta-reducers?
我使用自定义中间件 (meta-reducer) 在每次 dipatched 操作时打印我的 ngrx-store
。我直接在 app.module.ts
里面写了我的中间件(我应该把它放在哪里?):
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 {}
出现错误,因为我的 LoggerService
注入了一个存储(因为我希望我所有的日志都存储在 ngx-store
中。但是我无法访问该存储!。此外,我确信这不是访问服务的单例实例的好方法...
- 我是否应该将我的 meta reducer 放在 class 中,然后访问那个 class 的函数,但我该怎么做呢?
- 是否有访问任何服务的通用方法,例如
SomeClass.getServiceInstance(type)
?
- 你有其他的想法吗?
[编辑 1] - 使用 META_REDUCERS
(第一次尝试 - 失败)
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 {}
根据文档,这应该可以工作,但我在运行时遇到以下错误:
TypeError: "fn is not a function"
对应这个函数(在njrx-store库中):
/**
* @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));
});
}
在调试器中,显示函数数组(...functions
)包含一些函数和一个数组,我怀疑这是方法getMetaReducers
的结果。我怀疑这个例子是错误的,或者 compose
方法的实现有问题。
如果您在我的代码中发现任何错误,请告诉我。
[编辑 2] - 使用 USER_PROVIDED_META_REDUCERS
如答案中所述(第二次尝试 - 失败)
已编辑的代码
// OLD
providers: [
// ...
{
provide: META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
multi: true,
},
],
// NEW
providers: [
// ...
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
},
],
我的 LoggerService
似乎没有正确传递也没有初始化,因为我现在有这个错误:
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)
如果我评论这些行:
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
不会抛出异常,但我的记录器也不会工作。
但至少商店自己配置正确,现在的问题只是 LoggerService 没有正确传递或初始化。我想我还是做错了什么
您应该尝试使用 META_REDUCERS
标记作为 documented:
注入 meta-reducer
(注意:从 24.Sept 开始,文档有点误导,工厂方法 return 类型应该是 MetaReducer
并且不是 MetaReducer[]
。检查 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 {}
更新:
Since v8 您可以使用 ÙSER_PROVIDED_META_REDUCERS
令牌:
export function getMetaReducers(logger: LoggerService): MetaReducer<AppState>[] {
return [debugFactory(logger)];
}
providers: [
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers
},
],
在这种情况下,提供的工厂方法必须 return MetaReducer[]
。 "library" 和 "user" 提供的 meta-reducer 在 this function.
中合并为一个集合
您很可能会遇到商店和记录器服务之间的循环依赖,因为在创建商店期间,angular的 IOC 容器将尝试实例化一个记录器服务,这取决于商店, 在商店创建完成之前。您可以通过 "lazily" 访问记录器服务中的商店来解决此问题,方法是将注入器注入其中并为商店定义 getter 属性,例如:
private get() store{return this.injector.get(Store);}
ctor(private readonly injector: Injector){}
我使用自定义中间件 (meta-reducer) 在每次 dipatched 操作时打印我的 ngrx-store
。我直接在 app.module.ts
里面写了我的中间件(我应该把它放在哪里?):
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 {}
出现错误,因为我的 LoggerService
注入了一个存储(因为我希望我所有的日志都存储在 ngx-store
中。但是我无法访问该存储!。此外,我确信这不是访问服务的单例实例的好方法...
- 我是否应该将我的 meta reducer 放在 class 中,然后访问那个 class 的函数,但我该怎么做呢?
- 是否有访问任何服务的通用方法,例如
SomeClass.getServiceInstance(type)
? - 你有其他的想法吗?
[编辑 1] - 使用 META_REDUCERS
(第一次尝试 - 失败)
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 {}
根据文档,这应该可以工作,但我在运行时遇到以下错误:
TypeError: "fn is not a function"
对应这个函数(在njrx-store库中):
/**
* @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));
});
}
在调试器中,显示函数数组(...functions
)包含一些函数和一个数组,我怀疑这是方法getMetaReducers
的结果。我怀疑这个例子是错误的,或者 compose
方法的实现有问题。
如果您在我的代码中发现任何错误,请告诉我。
[编辑 2] - 使用 USER_PROVIDED_META_REDUCERS
如答案中所述(第二次尝试 - 失败)
已编辑的代码
// OLD
providers: [
// ...
{
provide: META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
multi: true,
},
],
// NEW
providers: [
// ...
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers,
},
],
我的 LoggerService
似乎没有正确传递也没有初始化,因为我现在有这个错误:
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)
如果我评论这些行:
logger.storeInfo('ACTION', action);
logger.storeInfo('STATE', state);
不会抛出异常,但我的记录器也不会工作。
但至少商店自己配置正确,现在的问题只是 LoggerService 没有正确传递或初始化。我想我还是做错了什么
您应该尝试使用 META_REDUCERS
标记作为 documented:
(注意:从 24.Sept 开始,文档有点误导,工厂方法 return 类型应该是 MetaReducer
并且不是 MetaReducer[]
。检查 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 {}
更新:
Since v8 您可以使用 ÙSER_PROVIDED_META_REDUCERS
令牌:
export function getMetaReducers(logger: LoggerService): MetaReducer<AppState>[] {
return [debugFactory(logger)];
}
providers: [
{
provide: USER_PROVIDED_META_REDUCERS,
deps: [LoggerService],
useFactory: getMetaReducers
},
],
在这种情况下,提供的工厂方法必须 return MetaReducer[]
。 "library" 和 "user" 提供的 meta-reducer 在 this function.
您很可能会遇到商店和记录器服务之间的循环依赖,因为在创建商店期间,angular的 IOC 容器将尝试实例化一个记录器服务,这取决于商店, 在商店创建完成之前。您可以通过 "lazily" 访问记录器服务中的商店来解决此问题,方法是将注入器注入其中并为商店定义 getter 属性,例如:
private get() store{return this.injector.get(Store);}
ctor(private readonly injector: Injector){}