NgRx: Как использовать сервисы в метаредукторах? - PullRequest
1 голос
/ 23 сентября 2019

Я использую пользовательское промежуточное ПО ( мета-редуктор ), чтобы печатать мои 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 (1-я попытка - сбой)

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));
    });
}

В отладчике показано, что функцияon массив (...functions) содержит несколько функций и один массив, который, как я подозреваю, является результатом метода getMetaReducers.Я подозреваю, что либо пример неправильный, либо есть проблема с реализацией метода compose.

Скажите, если вы видите какие-либо неправильные вещи в моем коде.

[EDIT 2]- используя USER_PROVIDED_META_REDUCERS, как указано в ответе (2-я попытка - не удалось)

код, который был отредактирован

// 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 либо неправильно передана, ни инициализирована.Я думаю, я все еще делаю что-то не так

1 Ответ

2 голосов
/ 23 сентября 2019

Вы должны попробовать ввести мета-редукторы, используя токен META_REDUCERS, как задокументировано :

( HEADS UP : по состоянию на 24.Стать документывводит в заблуждение, тип возврата фабричного метода должен быть MetaReducer, а не MetaReducer[]. Проверьте этот пример в кодовой базе )

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 {}

Обновление:

Начиная с версии 8 вы можете использовать токен Ù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[].Предоставленные как «библиотека», так и «пользователь» метаредукторы объединены в одну коллекцию в этой функции .

Скорее всего, вы столкнетесь с циклической зависимостью между вашим магазином и сервисом ведения журнала, как во времяПри создании магазина контейнер IOC angular попытается создать службу регистрации, которая зависит от магазина, до того, как создание магазина будет завершено.Вы можете решить эту проблему, «лениво» получая доступ к хранилищу в службе ведения журнала, вставив в него Инжектор и определив свойство получателя для хранилища, что-то вроде:

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

ctor(private readonly injector: Injector){}
...