Gabriel Katz
map: Apply function to each emitted value.
filter: Only return emitted values that fulfill predicate.
scan: Like a reduce, but emits intermediate result.
mergeMap: Create observable for each emitted value, and merge observables
switchMap: Only listen to observable created from most current emitted value
From https://brigade.engineering/what-is-the-flux-application-architecture-b57ebca85b9e
From https://brigade.engineering/what-is-the-flux-application-architecture-b57ebca85b9e
From https://brigade.engineering/what-is-the-flux-application-architecture-b57ebca85b9e
export type Reducer<S, A> = (state: S, action: A) => S;
@Injectable()
export class Store<S, A> extends BehaviorSubject<S>{
private actions = new Subject<A>();
constructor(private reducer: Reducer<S, A>, public initialState: S) {
super(initialState);
this.actions.scan((state, action) => this.reducer(state, action), initialState)
.subscribe(super);
}
dispatch(action: A): void {
this.actions.next(action);
}
}
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';
export class IncrementCounterAction implements Action{
readonly type = INCREMENT;
constructor(public payload: number = 1){
}
}
actions/counter.ts
export function counterReducer(state: number = 0, action: Action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
case RESET:
return 0;
default:
return state;
}
}
reducers/counter.ts
Testing a reducer is easy!
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(mainReducer),
// pre-4.x
StoreModule.provideStore(mainReducer)
]
})
export class AppModule {}
interface AppState {
counter: number;
}
export const mainReducer = { counter: counterReducer };
reducers/index.ts
app.module.ts
@Component({
selector: 'my-app',
changeDetection: ChangeDetectionStrategy.OnPush,
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((s: AppState) => s.counter);
}
increment(){
this.store.dispatch(new IncrementCounterAction());
}
...
}
class MyAppComponent {
constructor(private store: Store<AppState>){
this.counter$ = store.select(getCounter);
}
}
export const getCounter = (state: AppState) => state.counter;
reducers/index.ts
Q: How does this approach scale?
interface PartState {
foo: A;
bar: B;
}
export partReducer = combineReducers({ foo: fooReducer, bar: barReducers })
@Injectable()
export class CollectionEffects {
@Effect()
addBookToCollection$: Observable<Action> = this.actions$
.ofType(collection.ADD_BOOK)
.map((action: collection.AddBookAction) => action.payload)
.mergeMap(book =>
this.db.insert('books', [ book ])
.map(() => new collection.AddBookSuccessAction(book))
.catch(() => of(new collection.AddBookFailAction(book)))
);
constructor(private actions$: Actions, private db: Database) { }
}
@NgModule({
imports: [
BrowserModule,
StoreModule.provideStore(...),
EffectsModule.forRoot([CollectionEffects]),
// pre-4.x.x
EffectsModule.run(CollectionEffects)
]
})
export class AppModule {}
describe('collection effects', () => {
let collectionEffects: CollectionEffects;
let effectsRunner: EffectsRunner;
beforeEach(() => TestBed.configureTestingModule({
imports: [
EffectsTestingModule
],
providers: [
CollectionEffects,
...
]
}));
beforeEach(() => { /* assign variables from TestBed.get(...) */});
it('should load collection', () => {
...
effectsRunner.queue(new AddBookAction());
collectionEffects.addBookToCollection$.subscribe(result => {
expect(result).toEqual(expectedResult);
});
});
});
Goal: Synchronize router state and store state
Store
Router
dispatch( go(['foo']))
navigate(['foo'])
UPDATE_LOCATION()
Router
Store
dispatch(
ROUTER_NAVIGATION)
navigate(['foo'])
Action will be cancellable by throwing exception!
=> DEMO!
Emil Effila
Nino Lanfranchi
André Fröhlich
everyone else at ti&m
everyone at Triarc Laboratories
Dibran Isufi
Marcel Tinner