Evan Schultz
Developer @Rangleio
Some rights reserved - Creative Commons 2.0 by-sa
BUILDING ANGULAR 2
APPLICATIONS
WITH REDUX
A Typical Complex SPA
• Lots of parts.
• Everything is connected to everything else.
• Changing anything breaks something somewhere.
Best Solutions Known as of Now
• Component-based UI.
• Unidirectional data-flow.
• A “stateless” approach deeper in the stack.
Stateless Architecture
Why Stateless is Good
• A lot of us were raised on OOP. Objects are stateful.
• The effect of calling a method depends on the arguments and
the object’s state.
• Very painful to test.
• Very hard to understand.
• Aims to mimic the real world.
• But why replicate the unnecessary complexity?
Alternative: Pure Functions
• The output of a pure function depends only on inputs.
• Pure functions have no side-effects.
• Pure functions have no state.
• Much easier to understand.
• Much easier to test.
• Very easy to build up through composition.
• Widely used on the server side today.
How State Really Becomes Painful
Component Model
Model
Model
Model
Component
Component
Component
Unidirectional Data Flow with Flux
Component
Store
API, etc.
Component
Dispatcher
Action(s)Action

Creator
Can We Do Better?
Getting More Stateless with Redux
• https://github.com/rackt/redux
• Key concepts: a single store + state “reducers”.
• Can a single store be a good idea?
• What the heck is a “reducer”?
Reducer Functions
• Basic reduce():
function addOneValue (value, state) {
return state + value;
}
[1,2,3,4,5].reduce(addOneValue, 0);
• Running through the items:
1: 0 => 1
2: 1 => 3
3: 3 => 6
4: 6 => 10
5: 10 => 15
Action Reducers in Redux
• Take an action and a state, return a new state.
• Your app’s state is a reduction over the actions.
• Each reducer operates on a subset of the global state.
• Simple reducers are combined into complex ones.
An Example
export const lineupReducer = (state: ILineup[] = INITIAL_STATE,
action) => {
switch (action.type) {
case PARTY_JOINED: return [...state, action.payload];
case PARTY_LEFT: return state
.filter(n => n.partyId !== action.payload.partyId);
case PARTY_SEATED: return state
.filter(n => n.partyId !== action.payload.partyId);
default: return state;
}
};
Testing Is Easy
describe('the lineup reducer', () => {
it('should allow parties to join the lineup', () => {
const initialState = lineupReducer([]);
const expectedState = [{ partyId: 1, numberOfPeople: 2 }];
const partyJoined = {
type: PARTY_JOINED,
payload: { partyId: 1, numberOfPeople: 2 }
};
const nextState = lineupReducer(initialState, partyJoined);
expect(nextState).to.deep.equal(expectedState);
});
});
Why Is This a Good Idea?
• Reducers are pure functions – easy to understand, easy to test.
• Reducers are synchronous.
• Data logic is 100% separated from view logic.
• You can still have modularity by combining reducers.
• New opportunities for tools.
How Do We Avoid Mutating State?
• Reducers are supposed to not change the old state. But how do
we keep them honest?
• Immutable data structures store data without introducing
“state”.
• Object.freeze() - shallow.
• “Seamless Immutable”: frozen all the way down.
• But what happens when you want to modify your data?
Derived Immutables
• You can’t change immutable objects. But you need to.
• So, you derive new ones. I.e., make a new immutable that is
different in a specified way, without altering the original.
• “Immutable.JS” is the library to use.
var newData = data.setIn(
['foo', 'bar', 'baz'],
42
);
• This is a key building block for stateless architecture.
Change Detection
• Angular 2 has OnPush change detection
• Only fires when reference to an Input changes
• Do not need to do property by property checking
• If used properly, can improve the performance of your
application
Angular 2 + Redux
=
ng2-redux
ng2-redux
• Angular 2 bindings for Redux
• https://github.com/angular-redux/ng2-redux
• npm install ng2-redux
• Store as Injectable service
• Expose store as an Observable
• Compatible with existing Redux ecosystem
• https://github.com/xgrommx/awesome-redux
Angular 2 - Register the Provider
import { bootstrap } from '@angular/platform-browser-dynamic';
import { NgRedux } from 'ng2-redux';
import { RioSampleApp } from './containers/sample-app';
import { ACTION_PROVIDERS } from './actions';
bootstrap(RioSampleApp, [
NgRedux,
ACTION_PROVIDERS
]);
Angular 2 - Configure the Store
import { combineReducers } from 'redux';
import { IMenu, menuReducer } from './menu';
import { ITables, tableReducer } from './tables';
import { ILineup, lineupReducer } from './lineup';
export interface IAppState {
lineup?: ILineup[];
menu?: IMenu;
tables?: ITables;
};
export default combineReducers<IAppState>({
lineup: lineupReducer,
menu: menuReducer,
tables: tableReducer
});
Angular 2 - Configure the Store
import { NgRedux } from 'ng2-redux';
import { IAppState } from '../reducers';
import rootReducer from '../reducers';
import { middleware, enhancers } from '../store';
@Component({
selector: 'rio-sample-app',
// ...
})
export class RioSampleApp {
constructor(private ngRedux: NgRedux<IAppState>) {
ngRedux
.configureStore(rootReducer, {}, middleware, enhancers);
}
};
Angular 2 - Dumb Component
• Receives data from container or smart component
@Input() lineup: ILineup[];
• Emits events up to the parent
@Output() partyJoined: EventEmitter<any> = new EventEmitter();
• Responsible for rendering only
Angular 2 - Create a Component
@Component({
selector: 'tb-lineup',
template: TEMPLATE,
changeDetection: ChangeDetectionStrategy.OnPush,
directives: [REACTIVE_FORM_DIRECTIVES]
})
export class Lineup {
@Input() lineup: ILineup[];
@Output() partyJoined: EventEmitter<any> = new EventEmitter();
@Output() partyLeft: EventEmitter<any> = new EventEmitter();
};
Angular 2 - Create a Template
<tr *ngFor="let party of lineup">
<td>{{party.partyId}}</td>
<td>{{party.numberOfPeople}}</td>
<td>{{party.partyName}}</td>
<td>
<button type="button"
(click)="partyLeft.emit({partyId: party.partyId})">X
</button>
</td>
</tr>
Angular 2 - Container Component
• Knows about Redux
• Responsible for getting the data from the store
• Responsible for passing down data
• Responsible for dispatching actions
• Nest where appropriate
@Component({
selector: 'tb-home',
template: `<tb-lineup [lineup]="lineup$ | async"
(partyJoined)="partyJoined($event)"
(partyLeft)="partyLeft($event)">
</tb-lineup>`,
directives: [Lineup, RioContainer, Panel, Table, Menu]
})
export class HomePage {
// ...
}
Angular 2 - Container Template
export class HomePage {
constructor(private _ngRedux: NgRedux<IAppState>,
private _lineupActions: LineupActions) { }
partyJoined({numberOfPeople, partyName}) {
this._lineupActions.joinLine(numberOfPeople, partyName);
}
partyLeft({partyId}) {
this._lineupActions.leaveLine(partyId);
}
Angular 2 - Container Class
Angular 2 - ngRedux.select
export class HomePage {
constructor(private _ngRedux: NgRedux<IAppState>) { }
/* ... */
ngOnInit() {
this.lineup$ = this._ngRedux.select('lineup');
this.lineupByFunction$ = this._ngRedux
.select(state => state.lineup);
this.observable = this._ngRedux
.select(state=>state.lineup)
.map(line=>line.filter(n => n.numberOfPeople >= 4))
.combineLatest(/*....*/)
}
Angular 2 - @select
export class HomePage {
@select() lineup$: Observable<ILineup[]>;
@select('lineup') lineupByKey$: Observable<ILineup[]>;
@select(state => state.lineup)
lineupByFunction$: Observable<ILineup[]>;
}
Angular 2 - Dispatching Actions
• ngRedux.dispatch
• Works with Redux middleware
• Can dispatch from component, or ActionServices
• Final action that is sent to the reducer is a plain JSON object
Angular 2 - From a Component
@Component({ /* ... */})
export class HomePage {
constructor(private _ngRedux: NgRedux<IAppState>) { }
partyJoined({numberOfPeople, partyName}) {
this._ngRedux.dispatch<any>(joinLine(numberOfPeople, partyName));
}
};
import { joinLine } from ‘../actions';
Angular 2 - ActionServices
• Injectable services
• Can access your other Angular 2 Services
• Access to NgRedux and it’s store methods
• subscribe
• getState
• dispatch
• etc …
Angular 2 - Synchronous Actions
@Injectable()
export class LineupActions {
constructor(private _ngRedux: NgRedux<IAppState>,
private _party: PartyService) { }
leaveLine(partyId) {
this._ngRedux.dispatch({
type: PARTY_LEFT,
payload: { partyId }
});
}
}
Handling Asyncronous Actions
• Call an action creator to initiate a request.
• Action creator emits an action to inform everyone that a
request was made.
• Action creator emits an action to inform everyone that a
request was completed. (Or when it fails.)
• Push details of making a request into a module.
Angular 2 - Async Actions
@Injectable()
export class LineupActions {
constructor(private _ngRedux: NgRedux<IAppState>,
private _party: PartyService) { }
joinLine(numberOfPeople, partyName) {
this._ngRedux.dispatch({ type: PARTY_JOINING });
this._party.getNextPartyId()
.then(partyId => this._ngRedux.dispatch({
type: PARTY_JOINED,
payload: { partyId, numberOfPeople, partyName }})
).catch(err => {
this._ngRedux.dispatch({ type: PARTY_JOIN_ERR,
payload: err });
});
};
}
Demo Time
Demo
• TrendyBrunch
• https://github.com/e-schultz/ng2-camp-example
Caveats
• Its addictive.
• You won’t be happy using anything else.
• Your friends might not understand your obsession.
THANK YOU!
Evan Schultz
@e_p82
e-schultz
Developer, rangle.io

Evan Schultz - Angular Camp - ng2-redux

  • 1.
    Evan Schultz Developer @Rangleio Somerights reserved - Creative Commons 2.0 by-sa BUILDING ANGULAR 2 APPLICATIONS WITH REDUX
  • 2.
    A Typical ComplexSPA • Lots of parts. • Everything is connected to everything else. • Changing anything breaks something somewhere.
  • 3.
    Best Solutions Knownas of Now • Component-based UI. • Unidirectional data-flow. • A “stateless” approach deeper in the stack.
  • 4.
  • 5.
    Why Stateless isGood • A lot of us were raised on OOP. Objects are stateful. • The effect of calling a method depends on the arguments and the object’s state. • Very painful to test. • Very hard to understand. • Aims to mimic the real world. • But why replicate the unnecessary complexity?
  • 6.
    Alternative: Pure Functions •The output of a pure function depends only on inputs. • Pure functions have no side-effects. • Pure functions have no state. • Much easier to understand. • Much easier to test. • Very easy to build up through composition. • Widely used on the server side today.
  • 7.
    How State ReallyBecomes Painful Component Model Model Model Model Component Component Component
  • 8.
    Unidirectional Data Flowwith Flux Component Store API, etc. Component Dispatcher Action(s)Action
 Creator
  • 9.
    Can We DoBetter?
  • 10.
    Getting More Statelesswith Redux • https://github.com/rackt/redux • Key concepts: a single store + state “reducers”. • Can a single store be a good idea? • What the heck is a “reducer”?
  • 11.
    Reducer Functions • Basicreduce(): function addOneValue (value, state) { return state + value; } [1,2,3,4,5].reduce(addOneValue, 0); • Running through the items: 1: 0 => 1 2: 1 => 3 3: 3 => 6 4: 6 => 10 5: 10 => 15
  • 12.
    Action Reducers inRedux • Take an action and a state, return a new state. • Your app’s state is a reduction over the actions. • Each reducer operates on a subset of the global state. • Simple reducers are combined into complex ones.
  • 13.
    An Example export constlineupReducer = (state: ILineup[] = INITIAL_STATE, action) => { switch (action.type) { case PARTY_JOINED: return [...state, action.payload]; case PARTY_LEFT: return state .filter(n => n.partyId !== action.payload.partyId); case PARTY_SEATED: return state .filter(n => n.partyId !== action.payload.partyId); default: return state; } };
  • 14.
    Testing Is Easy describe('thelineup reducer', () => { it('should allow parties to join the lineup', () => { const initialState = lineupReducer([]); const expectedState = [{ partyId: 1, numberOfPeople: 2 }]; const partyJoined = { type: PARTY_JOINED, payload: { partyId: 1, numberOfPeople: 2 } }; const nextState = lineupReducer(initialState, partyJoined); expect(nextState).to.deep.equal(expectedState); }); });
  • 15.
    Why Is Thisa Good Idea? • Reducers are pure functions – easy to understand, easy to test. • Reducers are synchronous. • Data logic is 100% separated from view logic. • You can still have modularity by combining reducers. • New opportunities for tools.
  • 16.
    How Do WeAvoid Mutating State? • Reducers are supposed to not change the old state. But how do we keep them honest? • Immutable data structures store data without introducing “state”. • Object.freeze() - shallow. • “Seamless Immutable”: frozen all the way down. • But what happens when you want to modify your data?
  • 17.
    Derived Immutables • Youcan’t change immutable objects. But you need to. • So, you derive new ones. I.e., make a new immutable that is different in a specified way, without altering the original. • “Immutable.JS” is the library to use. var newData = data.setIn( ['foo', 'bar', 'baz'], 42 ); • This is a key building block for stateless architecture.
  • 18.
    Change Detection • Angular2 has OnPush change detection • Only fires when reference to an Input changes • Do not need to do property by property checking • If used properly, can improve the performance of your application
  • 19.
    Angular 2 +Redux = ng2-redux
  • 20.
    ng2-redux • Angular 2bindings for Redux • https://github.com/angular-redux/ng2-redux • npm install ng2-redux • Store as Injectable service • Expose store as an Observable • Compatible with existing Redux ecosystem • https://github.com/xgrommx/awesome-redux
  • 21.
    Angular 2 -Register the Provider import { bootstrap } from '@angular/platform-browser-dynamic'; import { NgRedux } from 'ng2-redux'; import { RioSampleApp } from './containers/sample-app'; import { ACTION_PROVIDERS } from './actions'; bootstrap(RioSampleApp, [ NgRedux, ACTION_PROVIDERS ]);
  • 22.
    Angular 2 -Configure the Store import { combineReducers } from 'redux'; import { IMenu, menuReducer } from './menu'; import { ITables, tableReducer } from './tables'; import { ILineup, lineupReducer } from './lineup'; export interface IAppState { lineup?: ILineup[]; menu?: IMenu; tables?: ITables; }; export default combineReducers<IAppState>({ lineup: lineupReducer, menu: menuReducer, tables: tableReducer });
  • 23.
    Angular 2 -Configure the Store import { NgRedux } from 'ng2-redux'; import { IAppState } from '../reducers'; import rootReducer from '../reducers'; import { middleware, enhancers } from '../store'; @Component({ selector: 'rio-sample-app', // ... }) export class RioSampleApp { constructor(private ngRedux: NgRedux<IAppState>) { ngRedux .configureStore(rootReducer, {}, middleware, enhancers); } };
  • 24.
    Angular 2 -Dumb Component • Receives data from container or smart component @Input() lineup: ILineup[]; • Emits events up to the parent @Output() partyJoined: EventEmitter<any> = new EventEmitter(); • Responsible for rendering only
  • 25.
    Angular 2 -Create a Component @Component({ selector: 'tb-lineup', template: TEMPLATE, changeDetection: ChangeDetectionStrategy.OnPush, directives: [REACTIVE_FORM_DIRECTIVES] }) export class Lineup { @Input() lineup: ILineup[]; @Output() partyJoined: EventEmitter<any> = new EventEmitter(); @Output() partyLeft: EventEmitter<any> = new EventEmitter(); };
  • 26.
    Angular 2 -Create a Template <tr *ngFor="let party of lineup"> <td>{{party.partyId}}</td> <td>{{party.numberOfPeople}}</td> <td>{{party.partyName}}</td> <td> <button type="button" (click)="partyLeft.emit({partyId: party.partyId})">X </button> </td> </tr>
  • 27.
    Angular 2 -Container Component • Knows about Redux • Responsible for getting the data from the store • Responsible for passing down data • Responsible for dispatching actions • Nest where appropriate
  • 28.
    @Component({ selector: 'tb-home', template: `<tb-lineup[lineup]="lineup$ | async" (partyJoined)="partyJoined($event)" (partyLeft)="partyLeft($event)"> </tb-lineup>`, directives: [Lineup, RioContainer, Panel, Table, Menu] }) export class HomePage { // ... } Angular 2 - Container Template
  • 29.
    export class HomePage{ constructor(private _ngRedux: NgRedux<IAppState>, private _lineupActions: LineupActions) { } partyJoined({numberOfPeople, partyName}) { this._lineupActions.joinLine(numberOfPeople, partyName); } partyLeft({partyId}) { this._lineupActions.leaveLine(partyId); } Angular 2 - Container Class
  • 30.
    Angular 2 -ngRedux.select export class HomePage { constructor(private _ngRedux: NgRedux<IAppState>) { } /* ... */ ngOnInit() { this.lineup$ = this._ngRedux.select('lineup'); this.lineupByFunction$ = this._ngRedux .select(state => state.lineup); this.observable = this._ngRedux .select(state=>state.lineup) .map(line=>line.filter(n => n.numberOfPeople >= 4)) .combineLatest(/*....*/) }
  • 31.
    Angular 2 -@select export class HomePage { @select() lineup$: Observable<ILineup[]>; @select('lineup') lineupByKey$: Observable<ILineup[]>; @select(state => state.lineup) lineupByFunction$: Observable<ILineup[]>; }
  • 32.
    Angular 2 -Dispatching Actions • ngRedux.dispatch • Works with Redux middleware • Can dispatch from component, or ActionServices • Final action that is sent to the reducer is a plain JSON object
  • 33.
    Angular 2 -From a Component @Component({ /* ... */}) export class HomePage { constructor(private _ngRedux: NgRedux<IAppState>) { } partyJoined({numberOfPeople, partyName}) { this._ngRedux.dispatch<any>(joinLine(numberOfPeople, partyName)); } }; import { joinLine } from ‘../actions';
  • 34.
    Angular 2 -ActionServices • Injectable services • Can access your other Angular 2 Services • Access to NgRedux and it’s store methods • subscribe • getState • dispatch • etc …
  • 35.
    Angular 2 -Synchronous Actions @Injectable() export class LineupActions { constructor(private _ngRedux: NgRedux<IAppState>, private _party: PartyService) { } leaveLine(partyId) { this._ngRedux.dispatch({ type: PARTY_LEFT, payload: { partyId } }); } }
  • 36.
    Handling Asyncronous Actions •Call an action creator to initiate a request. • Action creator emits an action to inform everyone that a request was made. • Action creator emits an action to inform everyone that a request was completed. (Or when it fails.) • Push details of making a request into a module.
  • 37.
    Angular 2 -Async Actions @Injectable() export class LineupActions { constructor(private _ngRedux: NgRedux<IAppState>, private _party: PartyService) { } joinLine(numberOfPeople, partyName) { this._ngRedux.dispatch({ type: PARTY_JOINING }); this._party.getNextPartyId() .then(partyId => this._ngRedux.dispatch({ type: PARTY_JOINED, payload: { partyId, numberOfPeople, partyName }}) ).catch(err => { this._ngRedux.dispatch({ type: PARTY_JOIN_ERR, payload: err }); }); }; }
  • 38.
  • 39.
  • 40.
    Caveats • Its addictive. •You won’t be happy using anything else. • Your friends might not understand your obsession.
  • 41.