ngrx / platform

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

How to use just a *single* reducer function? #260

Closed martinleopold closed 7 years ago

martinleopold commented 7 years ago

I'm submitting a...


[x] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[x] Support request

What is the current behavior?

I'm upgrading an app from ngrx/store v2 to 4.

Now, when bootstrapping viaStoreModule.forRoot(), I'm expected to provide a map of reducer functions.

Expected behavior:

In the old app I was able to simply use a single reducer function with provideStore(singleReducer, initialState).

Is there a way I can emulate this behaviour in ngrx 4?

Should I just use StoreModule.forRoot({ 'root': singleReducer })? Then I'd need to change all my Store.select() calls to use that 'root' prefix, and thus add quite a bit of clutter, right?

Version of affected browser(s),operating system(s), npm, node and ngrx:

@ngrx/store 4.0.2 (migrating from @ngrx/store 2.0.1 + @ngrx/core 1.0.1)

brandonroberts commented 7 years ago

Yes, you should create a single piece of state in the map of reducers. Depending on how your selectors are structured, you can just add a higher order selector that selects the top level and have your child selectors use it. Store has always been geared towards apps with more than one piece of state, so you'd be in the minority in this case.

deftomat commented 7 years ago

@brandonroberts Same here. We need rootReducer, which will have access to two subtrees because it needs to associate a normalized entity with a currently logged in user.

{
  session: { userId: 1 },
  entities: {
    services: {
      1: {approvedBy: null}
    }  
  }
}

to

{
  session: { userId: 1 },
  entities: {
    services: {
      1: {approvedBy: 1}
    }  
  }
}

To be honest, I don't understand why we cannot pass just a simple function. It makes NGRX soooo inflexible. Moving the whole state into root object is not a solution. Its a hack which requires using a high order selector across the whole app.

The only reason why there is this limitation is probably this. And to be honest, it is another strange behaviour, why reducers are not just a function and addReducer will combine old function with new function.

We all know that plain functions are much more flexible and this small object just brings a lot of headache. 😞

So, a question is: Is this really necessary? Features already supports simple functions.

brandonroberts commented 7 years ago

@deftomat Yes, plain functions are flexible, but the use case you provide is not a normal use case. NgRx is geared more towards building apps with multiple reducer states. It is flexible enough to handle your case also though. Store uses a reducerFactory that you can override. By default it uses combineReducers with your map of reducers. If you want to use a reducer function instead you provide this through the configuration option.

export function customReducerFactory() {
  // return root reducer
}

StoreModule.forRoot({}, {
  reducerFactory: customReducerFactory
})

Here is an example plunker also: https://plnkr.co/edit/8kmzmJ?p=info

deftomat commented 7 years ago

@brandonroberts Awesome, thanks for a super fast response. I'm happy with this flexibility.

So, as our use case is not normal. What is a suggested approach when reducer needs a data from another state's subtree?