How do you compose an AppState with reducers for multiple resources / components?

I'm trying to figure out how to combine many resource states into different component states and what AppState is. Most ngrx tutorials / examples only contain a resource (like a book) or a limited state (like books and a selected book), but I don't think I'm running into anything more complicated.

What do you do when you have a dozen resources with different states (list, item, search terms, menu items, filters, etc.) in multiple components that require different resource states?

I searched around and I came up with the following structure, but I'm not sure if this was intended:

AppState & reducer
<- combine reducers
- Component states & reducers
<- combine reducers
-- Resource states & reducers

      

You would combine resource reducers (e.g. bookReducer, booksReducer, bookSearchTitleReducer) into a component specific reducer (e.g. bookSearchReducer) and then combine all component reducers into one reducer with one AppState and use the store provider with that in your AppModule.

Is this the way to go or is there another (correct) way to do it? And if this is a good way to do it, would I use Store or Store in the Component constructor?

[change]

Ok, the ngrx-example-app example handles more components, I can see that it only creates states at the component level and not at the resource level, merges the states and their corresponding reducers, and uses the full state object in the component constructor: 'store: Store'.

I suppose this is an official example, this would be the intended way of handling states / reducers.

+3


source to share


1 answer


[change]

The new v4 ngrx is much easier to use, has a better documentation and example app to help you. This is mostly about v2 and these are quirks that are no longer an issue in version 4.

[Obsolete]

After a lot of trial and error, I found a good working formula. I'm going to share it with this, maybe it will help someone.

The reducer composition guide helped me a lot and convinced me to go for my original state / reducer structure Resource> Component> App. The guide is too big to fit here and you will most likely need an updated version here .

This quickly accomplishes what I had to do in some key files for a two-component application, with two main resources (user and asset) with derivatives (lists) and parameters (search).

store/reducers/user/index.ts:

import { ActionReducer, combineReducers } from '@ngrx/store';

import { authenticatedUserReducer } from './authenticatedUser.reducer';
import { selectedUserReducer } from './selectedUser.reducer';
import { userListReducer } from './userList.reducer';
import { userSearchReducer } from './userSearch.reducer';
import { User } from '../../models';

const reducers = {
  authenticated: authenticatedUserReducer,
  selected: selectedUserReducer,
  list: userListReducer,
  search: userSearchReducer
};

interface UserState {
  authenticated: User,
  selected: User,
  list: User[],
  search: string
}

const reducer: ActionReducer<UserState> = combineReducers(reducers);

function userReducer(state: any, action: any) {
  return reducer(state, action);
}

export { userReducer, UserState };

      

store/reducers/asset/index.ts:

import { ActionReducer, combineReducers } from '@ngrx/store';

import { selectedAssetReducer } from './selectedAsset.reducer';
import { assetListReducer } from './assetList.reducer';
import { assetSearchReducer } from './assetSearch.reducer';
import { Asset } from '../../models';

const reducers = {
  selected: selectedAssetReducer,
  list: assetListReducer,
  search: assetSearchReducer
};

interface AssetState {
  selected: Asset,
  list: Asset[],
  search: string
}

const reducer: ActionReducer<AssetState> = combineReducers(reducers);

function assetReducer(state: any, action: any) {
  return reducer(state, action);
}

export { assetReducer, AssetState };

      



store/reducers/index.ts:

import { routerReducer, RouterState } from '@ngrx/router-store';

import { userReducer, UserState } from './user';
import { assetReducer, AssetState } from './asset';

const reducers = {
  router: routerReducer,
  user: userReducer,
  asset: assetReducer
};

interface AppState {
  router: RouterState,
  user: UserState,
  asset: AssetState
}

export { reducers, AppState };

      

Note. I have included the router reducer separately.

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';

import { StoreModule } from '@ngrx/store';
import { RouterStoreModule } from '@ngrx/router-store';

import { reducers } from './store';
import { AppComponent } from './app.component';
import { AppRoutes } from './app.routes';
import { HomeComponent } from './components/home/home.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(AppRoutes),
    StoreModule.provideStore(reducers),
    RouterStoreModule.connectRouter()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

      

Note. Use what you need, drop what you don't have. I made another index.ts file inside / store, it will export reducers, all models and maybe some other stuff in the future.

home.component.ts:

import { Component } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';

import { AppState, User } from '../../store';

@Component({
  selector: 'home',
  templateUrl: './home.template.html'
})
export class HomeComponent {
  user: Observable<User>;

  constructor (private store: Store<AppState>) {
    this.user = store.select('user', 'selected');
    store.dispatch({ type: 'SET_USER_NAME', payload: 'John' });
    store.dispatch({ type: 'ADD_USER_ROLE', payload: 'admin' });
    store.dispatch({ type: 'ADD_USER_ROLE', payload: 'wordsmith' });
  }
}

      

Note. You can test with something like this {{(user | async)?.name}}

in your template.

And about that. There might be better ways to do this, I know I could only do this with one level (for example, just the core resources), all in line with what you think is right for your application.

+4


source







All Articles