-
Notifications
You must be signed in to change notification settings - Fork 311
Provide multiple reducers from multiple NgModules #211
Comments
There isn't yet, but this is definitely something I want to implement. Sketched in my head I have something like //app.module.ts
imports: [
StoreModule.provideRootStore({foos, bars})
]
//feature module
imports: [
StoreModule.addReducers({ bazes })
] This is reasonably straightforward to do if you're passing ngrx/store an object of your reducers, and significantly more difficult if you're combining reducers yourself. Entirely open to suggestions |
Yeah, this would be super useful in terms of modularity. So each module can be self contained and can be (re)moved easily. Some things to consider:
|
the reducer/state naming is a good point. If I import a feature module it would be nice if its not necessary to think about naming. Maybe at some point there are modules out there where I do not have control over the naming? Over all I really like this idea. Would leverage the module system much more. |
But you should also consider that you have some |
I've been tinkering with this today, and haven't found a decent way to make this work for both modes (pass an object and pass a combinedReducer), since the latter form means I never have an object of reducers I can augment or update. I'd really like to hear use cases of why people are using Collisions are definitely possible. We could switch to using a Map (rather than an object) internally, which would let us use classes or Symbols as keys, but again, is a bit breaky on the API surface. |
Could you elaborate on "pass an object and pass a combinedReducer"? I don't understand that. The reason will (probably) always be: Inversion of Control and clean modules With the current version there has to be a global store configuration where you include and combine all reducers for the application. I think that's a bad architecture design in terms of modularization. Especially if there's an depencendy injection framework available. Using IoC you could do something like |
I've started to embrace the move to ng-modules and I like how organized everything becomes thanks to that. I have refactored my app to make use of modules lazy loading, unfortunately I don't know how to do the same with reducers/actions/effects. Ideally I would like to be able to move these services into the corresponding module folders and have them lazy load together with the rest (just declare them in that module with My app has the following usecase (as probably most apps have): |
I've been playing with this and I came up with the following. (jsbin: http://jsbin.com/junini/38/edit?js,console) If you pass an object (or a combined reducer) to your store, you'll reducer tree will look like this. Let's say you want to add something to this reducer tree. Instead of thinking about this as passing an extra reducer to the current reducer tree, you could think of it as creating a separate tree for the thing you are adding and combine the result of those two trees. Conceptually speaking this would look like this. This way, you do not really need to know anything about the previous reducers and it doesn't matter if it was created by calling the combineReducers method directly or inside the store. The 'only' thing that needs to be added to implement this is another meta-reducer called enhanceReducers (or something similar). What it will do, when called, is slice up the state tree and pass the part of the tree that belongs to the first reducer to the first, and the second to the second. The results are then again combined into a single object and returned. To know what part of the state tree needs to be passed to which reducer, it could, at creation time, send a dummy action to the current reducer and the new reducer. These will both return objects from which you can get the keys. These keys can later be used to slice up the current state object into the correct two slices to pass them to both reducers. To make this easier to understand. Let's divide the steps for the second image.
Let's say an action is dispatched to the enhancedReducer:
The jsbin example implements a first draft of this enhancedReducer as a proof of concept. Advantages:
Disadvantages:
Hope this makes some sense :). Let me know what you think... Edit: The jsbin example works with a todos- and usersReducer. These do not match the images above. |
@robwormald your proposal is really easy and flexible. @KwintenP had a right workaround approach, I've implemented a similar one: // app.module.ts
import { ActionReducer, combineReducers, StoreModule } from '@ngrx/store';
import { appRoutes } from './app.routes.ts';
// imports
@NgModule({
StoreModule.provideStore(AppModule.reducers()),
// other stuff
}
export class AppModule {
static reducers(): ActionReducer<any> {
return combineReducers(Object.assign({},
appReducer,
{router: routerReducer},
appRoutes.filter(route => (route.data && route.data['reducers'])).map(route => route.data['reducers']).pop()
));
}
}
// app.routes.ts
import { Routes } from '@angular/router';
import HomeModule from '../+home/home.module';
export const appRoutes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: 'home',
loadChildren: (() => new Promise((resolve, reject) => {
return resolve(require('../+home/home.module').default)
})),
data: {
reducers: HomeModule.reducers()
}
}
];
// home.module.ts
import { ActionReducer } from '@ngrx/store';
import { HomeComponent } from './home.component';
// imports
@NgModule({
// stuff
})
export default class HomeModule {
static reducers(): {[key: string]: ActionReducer<any>} {
return {home: homeReducer};
}
} Combine modules with routing allow to import childs modules once. |
First of all, thanks for ngrx 👍 Just wondering, is there an update on the go-forward/proposed pattern for handling ngrx in a multi-module setting with lazy-loading? I tend to recall, I came across a talk by @robwormald recorded some time earlier this year, where this question came up. I wish my memory served me better as to the when & where, but I think there was chatter of still looking into the necessary changes "without breaking too much" for the installed base... Thanks in advance. |
Update on this: @MikeRyan52 has an implementation that LGTM, so we'll go with that |
Here is a brief overview of the changes we will be making to @ngrx/store: https://gist.github.com/MikeRyan52/5d361681ed0c81e38775dd2db15ae202 |
As I don't see a mention of this in the document: Please keep lazy loaded modules in mind. :) |
It works, but does it compromises the chunking of modules? How a module can be lazily loaded in the form of chunks with combineReduce option. I am using webpack for chunking. |
@arunsn43 You could use something like the createReducer in this comment #197 (comment) In your lazy loaded module you would use |
Just an update on this, I'm back onto working on this API. Chatted w/ @victorsavkin a bit and we have a slightly tweaked implementation of Mike's PR that's a little more in keeping with how Angular's DI works - closer to how @KwintenP is describing it above. This would handle lazy loading of reducers in the same fashion the router handles modules, and keeps the same semantics as far as visibility. |
@alaycock / @robwormald Thank you! I've used the similar approach. It worked and now the modules are build and loaded in chunks. Also, added a method to remove the reducers as well as the state, on a need basis. But since the loaded angular module cannot be unloaded (unloading a lazily loaded module) , wondering what is the best option to fit this? (Components can be destroyed but not the angular module). My take is to associate with ngOnDestroy() of Angular module! Any thoughts?? |
Maybe late to the party, anyway if it might help: currently we call
const fooModuleReducer: ActionReducer<FooModuleState> = combineReducers({
remote: fooRemoteReducer,
local: fooLocalReducer,
});
const rootReducerSet = {
foo: fooModuleReducer,
bar: barModuleReducer,
baz: bazModuleReducer,
};
const rootReducer: ActionReducer<RootState> = combineReducers(originalRootReducers);
// same as non lazy-loaded module
const lazyModuleReducer: ActionReducer<LazyModuleState> = combineReducers({
remote: lazyRemoteReducer,
local: lazyLocalReducer,
});
const rootReducerPlusLazy: ActionReducer<RootStatePlusLazy> = combineReducers({
...rootReducerSet,
lazy: lazyModuleReducer,
}); |
@robwormald I'm also late to the party. Here is my use case for calling combineReducers in app code, rather than passing the object full of reduces to Store for it to combine: when coding as shown in the README of store-freeze: https://github.com/codewareio/ngrx-store-freeze (Side note - it sure would help the ergonomics of new developers adopting Store, if freezing was in the box and on by default, rather than being something to learn of eventually by looking around.) |
Is there some kind of time table for when this feature will be released? I see this conversation started September of last year. As our app has grown keeping all the reducers in the root module has been problematic. |
We're targeting July 1 for launch. In the meantime, see https://twitter.com/MikeRyanDev/status/856349359805485056 - we're publishing nightlies already if you want to try it out. Development will be moving (has moved) to the https://github.com/ngrx/platform/ repo |
@robwormald great to hear, thanks@! |
@robwormald Any info about a later release date? I have just started a new project and I would like to use the new ngrx. (I find it hard to code in the old one, and the examples of the new is really good in modularity.) I am asking beacuse if you could finish them e.g. within a week then I would use that instead of refactoring the code in 2 months. |
This is supported and implemented in v4 - https://github.com/ngrx/platform. Thanks! |
I'm wondering, if there's any built-in functionality to register additional reducers.
Use Case:
My application has multiple modules. Each module has its own
NgModule
declaration. And some of these modules use theStore
.I now created my own
Store
module, which provides a singlengrx/store
and then collects all my module stores via multi provider and then usescombineReducers
to register them. This works fine. But I guess this is a pretty common scenario and it would be nice ifngrx/store
would support this out of the box somehow.Here's what I do at the moment:
Then if some module needs a store:
It's basically just an inversion of what your Usage Example ( https://github.com/ngrx/store#usage ) does.
Is there already something like this built in
ngrx/store
?The text was updated successfully, but these errors were encountered: