The store is an observable "DB" to which components can subscribe for data.
The logic of the application is expressed as a function mapping an observable of actions into an observable of application states.
function stateFn(
initState: AppState,
actions: Observable<action>): Observable<AppState> { … }
The application and view logic are completely separated. The dispatcher and state objects are the boundary through which the application and the view communicate. The view emits actions using the dispatcher and listens to changes in the state.
export interface Reducer<T> {
(state: T, action: Action): T;
}
export const counter: Reducer<number> = (state: number = 0, action: Action) => {
switch(action.type){
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
import {Effect, Actions, toPayload} from "@ngrx/effects";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
@Injectable()
export class MainEffects {
constructor(private action$: Actions) { }
@Effect() update$ = this.action$
.ofType('SUPER_SIMPLE_EFFECT')
.switchMap( () =>
Observable.of({type: "SUPER_SIMPLE_EFFECT_HAS_FINISHED"})
);
}
// counter.ts
import { ActionReducer, Action } from '@ngrx/store';
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';
export function counterReducer(state: number = 0, action: Action) {
switch (action.type) {
case INCREMENT_SUCCESS:
return state + 1;
case DECREMENT_SUCCESS:
return state - 1;
case RESET:
return 0;
default:
return state;
}
}
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class CounterEffects {
constructor(
private http: Http,
private actions$: Actions
) { }
@Effect() increment$ = this.actions$
// Listen for the 'LOGIN' action
.ofType('INCREMENT')
// Map the payload into JSON to use as the request body
.map(action => JSON.stringify(action.payload))
.switchMap(payload => this.http.post('/counter')
// If successful, dispatch success action with result
.map(res => ({ type: 'INCREMENT_SUCCESS' }))
// If request fails, dispatch failed action
.catch(() => Observable.of({ type: 'INCREMENT_FAILED' }))
);
}
import { NgModule } from '@angular/core'
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { counterReducer } from './counter';
import { CounterEffects } from './effects';
@NgModule({
imports: [
BrowserModule,
StoreModule.provideStore({
counter: counterReducer
}),
EffectsModule.run(CounterEffects),
],
})
export class AppModule {}
import { Store } from '@ngrx/store';
import { INCREMENT, DECREMENT, RESET } from './counter';
interface AppState {
counter: number;
}
@Component({
selector: 'my-app',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button><button (click)="reset()">Reset Counter</button>
`
})
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>) { this.counter = store.select('counter'); }
increment() { this.store.dispatch({ type: INCREMENT }); }
decrement() { this.store.dispatch({ type: DECREMENT }); }
reset() { this.store.dispatch({ type: RESET }); }
}