5. 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?
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 Really Becomes Painful
Component Model
Model
Model
Model
Component
Component
Component
8. Unidirectional Data Flow with Flux
Component
Store
API, etc.
Component
Dispatcher
Action(s)Action
Creator
10. 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”?
12. 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.
13. 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;
}
};
15. 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.
16. 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?
17. 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.
18. 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
20. 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
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
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 …
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.