[Edit]
The new v4 ngrx is much simpler to use, has a better documentation and example app to help you out. The following is mostly relevant for v2 and its quirks, which are no longer an issue in v4.
[Obsolete]
After a lot of trial and error, I found a good, working formula. I'm going to share the gist of it here, maybe it'll help someone.
The guide on reducer composition helped me a lot and convinced me to go for my original state/reducer structure of Resource > Component > App. The guide is too large to fit here, and you will likely want the up to date version here.
Here's a quick run down of what I had to do in some key files for an app with two components, with two basic 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 included the router reducer supplied separately as well.
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 here, scrap what you don't. I made another index.ts file inside /store, it exports reducers, all the models and perhaps some other things 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: 'Jesse' });
store.dispatch({ type: 'ADD_USER_ROLE', payload: 'scientist' });
store.dispatch({ type: 'ADD_USER_ROLE', payload: 'wordsmith' });
}
}
Note: You can test with something like {{(user | async)?.name}}
in your template.
And that's about it. There may be better ways to do it, I know I could have made it with just one level for example (e.g. just the basic resources), it's all according to what you think fits best for your app.