【问题标题】:NgRx: How to use services in meta-reducers?NgRx:如何在 meta-reducers 中使用服务?
【发布时间】:2019-09-23 16:30:53
【问题描述】:

我使用自定义中间件 (meta-reducer) 来打印我的 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但我也无法访问该存储!。此外,我敢肯定这不是访问服务的单例实例的好方法...

  • 我应该把我的元减速器放在一个类中,然后访问那个类的函数,但是我该怎么做呢?
  • 是否有访问任何服务的通用方法,例如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 没有正确传递或初始化。我想我还是做错了什么

【问题讨论】:

  • “未定义”问题很可能与您的商店和记录器服务之间的循环依赖有关,因为在创建商店期间,angular 的 IOC 容器将尝试实例化记录器服务,这取决于商店,在商店的创建完成之前。您可以通过将Injector 注入其中并为存储定义一个getter 属性(例如private get() store{return this.injector.get(Store);})“懒惰地”访问记录器服务中的存储来解决此问题
  • 顺便说一句,你的逻辑有缺陷。通过您的 logger 服务在 meta-reducer 中调度一个动作将导致您的应用程序进入 reduce -> dispatch -> reduce -> dispatch 的无限状态.....我应该早点意识到这一点。
  • 抱歉,我看到你的回答完美地涵盖了它 - 删除了旧评论。我将添加进一步的评论,以帮助像我这样的人回答您的问题。谢谢!

标签: angular ionic-framework dependency-injection ionic4 ngrx


【解决方案1】:

您应该尝试使用META_REDUCERS 令牌作为documented 注入元减速器:

HEADS UP:截至 24.Sept 文档有点误导,工厂方法返回类型应为 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
  },
],

在这种情况下,提供的工厂方法必须返回MetaReducer[]。 “库”和“用户”提供的元归约器在 this function 中合并为一个集合。

你很可能会在你的 store 和 logger 服务之间遇到循环依赖,因为在创建 store 期间,angular 的 IOC 容器将在创建之前尝试实例化依赖于 store 的 logger 服务商店最终确定。您可以通过将 Injector 注入其中并为存储定义一个 getter 属性“懒惰地”访问记录器服务中的存储来解决此问题,例如:

private get() store{return this.injector.get(Store);}

ctor(private readonly injector: Injector){}

【讨论】:

  • 非常感谢,这似乎让我朝着好的方向前进,但不幸的是我的代码仍然无法正常工作。我用我的新代码更新了我的问题。
  • 这里是StackBlitz link如果你不想拉回购
  • 对于那些希望看到这一点的人,使用上面的StackBlitz link:app.module.ts:将META_REDUCERS 更改为USER_PROVIDED_META_REDUCERS并删除multi: truelogger.service.ts :将构造函数更改为:constructor(private readonly injector: Injector) {} private get store() { return this.injector.get(Store)}
  • @Jota.Toledo 是的,抱歉。我只是想通过给出的示例将其浓缩为一组易于遵循的步骤。非常感谢您的帮助。这是一个令人头疼的问题。
  • 注意!如果您想在功能级别进行 metareducer 依赖注入并且找不到方法,则它隐藏在文档中 here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-31
  • 1970-01-01
  • 2020-01-20
  • 2012-10-26
  • 2022-08-10
  • 1970-01-01
  • 2019-12-16
相关资源
最近更新 更多