ngrx / platform

Reactive State for Angular
https://ngrx.io
Other
8.03k stars 1.97k forks source link

Injecting meta-reducers #310

Closed laurentgoudet closed 7 years ago

laurentgoudet commented 7 years ago

As per the documentation, root reducers can be injected using an InjectionToken and a Provider to register the reducers through dependency injection.

Being able to do the same thing for meta-reducers is useful, as those often have side-effects like sending errors to a monitoring service, synchronizing state with sessionStorage.., which would benefit from Angular's DI framework.

This was apparently possible before v4 (https://github.com/ngrx/store/issues/313), but I couldn't find a way to do it now that the store handles the reducers composition. Is there a way to do that? Or could StoreConfig accept an InjectionToken<ActionReducer<T, V>>[] similarly to the forRoot's reducers param and inject the meta-reducers?

brandonroberts commented 7 years ago

Injection tokens for meta reducers already exist in the API. If you need lower level access to managing reducers, you should use the ReducerManager, as its what's used underneath to add/remove/replace reducers.

laurentgoudet commented 7 years ago

Thanks @brandonroberts, I may have missed something but StoreModule.forRoot() only takes a StoreConfigobject

export class StoreModule {
  static forRoot<T, V extends Action = Action>(
    reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,
    config?: StoreConfig<T, V>
  ): ModuleWithProviders;

whose type is

export type StoreConfig<T, V extends Action = Action> = {
  initialState?: InitialState<T>;
  reducerFactory?: ActionReducerFactory<T, V>;
  metaReducers?: MetaReducer<T, V>[];
};

Obviously TypeScript then complains that InjectionToken<MetaReducer<T, V>> isn't the same thing as MetaReducer<T, V>, although it won't work anyway since only INITIAL_REDUCERS and FEATURE_REDUCERS use injector.get().

And the ReducerManager addFeature & addReducer APIs would scope the reducer to a specific key / feature, so can't be used for meta reducers:

 addReducer(key: string, reducer: ActionReducer<any, any>) {
    this.reducers = { ...this.reducers, [key]: reducer };

    this.updateReducers();
  }

What's the proper way to achieve that? I've managed to make it work by manually composing my reducers using compose, it only feels weird that my code is taking care of reducer composition but I guess that's fine.

export function getReducers(errorHandler: ErrorHandler) {
  return compose.apply(null, [
        tryCatchReducerFactory(errorHandler),
  ])(reducers);
}
laurentgoudet commented 7 years ago

What's the proper way to achieve that? I've managed to make it work by manually composing my reducers using compose, it only feels weird that my code is taking care of reducer composition but I guess that's fine.

Actually that doesn't work since when using an InjectionToken StoreModule.forRoot still expect an reducers map, and not an already composed root reducer, so manual reducer composition can't do the trick. Am I missing something?

brandonroberts commented 7 years ago

@laurentgoudet I was referring to the META_REDUCERS token for register meta reducers in the root.

import { META_REDUCERS } '@ngrx/store';
import { SomeService } from './some.service';

export function getMetaReducers(some: SomeService): MetaReducer[] {
  // return array of meta reducers;
}

@NgModule({
  providers: [
    {
      provide: META_REDUCERS,
      deps: [SomeService],
      useFactory: getMetaReducers
    }
  ]
})
export class AppModule {}
laurentgoudet commented 7 years ago

Thanks for getting back to me, after re-reading it this was exactly what you said in your initial reply, and it indeed works perfectly. I need more coffee :).

brandonroberts commented 7 years ago

😄

samueledirito commented 6 years ago

@brandonroberts I'm sorry to reopen the conversation, but am I able to have the same behaviour on features modules? My metareducer is related to the feature I'm developing, so it's totally useless to me to have its registration in root module.

TIA

CleverCoder commented 6 years ago

I agree with @samueledirito. I need the ability to inject services into MetaReducers that are specified using .forFeature(...). Can this please be re-opened, or should we file another bug?

Thanks!