2. A Typical Complex SPA
• Lots of parts.
• Everything is connected to everything else.
• Changing anything breaks something somewhere.
• Managing state becomes a challenge.
3. How State Really Becomes Painful
Component Model
Model
Model
Model
Component
Component
Component
4. Best Solutions Known as of Now
• Component-based UI.
• Unidirectional data-flow.
• A “stateless” approach deeper in the stack.
6. 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?
7. 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.
8. Unidirectional Data Flow with Flux
Component
Store
API, etc.
Component
Dispatcher
Action(s)Action
Creator
13. Redux Key Concepts
• Application State is a single object in a single store.
• Only way to change state is by emitting an Action.
• Actions describe what happened.
• State is transformed by reducers that handle actions.
14. Reducer Functions
• Basic reduce():
function sum (value, state) {
return state + value;
}
[1,2,3,4,5].reduce(sum, 0);
• Running through the items:
1: 0 => 1
2: 1 => 3
3: 3 => 6
4: 6 => 10
5: 10 => 15
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.
16. An Example
export const cats = (state: ICats = INITIAL_STATE, action) => {
switch (action.type) {
case CAT_DELETED:
return state.filter(n => n.id !== action.payload.id);
case CAT_CREATED:
return [...state, action.payload];
case CAT_UPDATED:
return state.map(n => {
return n.id !== action.payload.id ? n :
Object.assign({}, n, action.payload); });
default:
return state;
}
};
18. 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.
19. Actions
• Information you send from your application to your store.
• The only source of change to your state.
• Represent what was done
• Simply JSON Objects with a ‘type’ property
21. Action Creators
• Functions that return actions.
• Can handle business logic
• Can handle API Calls and Async behaviour.
• Takes parameters and creates an Action
• Components should …
• Know what data to pass in.
• Not care about the structure of the payload.
23. Putting it together …
• Action Creators are the request to do something.
• Actions are the result of what happened.
• Reducers transform to represent what happened.
24. 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?
25. 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 one library to use.
var newData = data.setIn(
['foo', 'bar', 'baz'],
42
);
26. Derived Immutables
• These are key building blocks for stateless architecture.
• ES6 Spread Syntax return [...state, action.payload];
• Object.assign return Object.assign({}, n, action.payload);
27. 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.
33. Root Reducer
• Redux has a Global Application State.
• Root Reducer combines reducers into your Application State.
• Slices of state are handled by a reducer.
• Each Reducer can focus on a small area of concern.
38. Reducer Composition
export const rootReducer =
combineReducers<IAppState>
({
cats,
catEdit,
codeTables,
filters,
catsLoading
});
• Keep reducers small and
simple.
• Focus on one area.
• Compose as needed.
• Responsible for Data
Management.
39. Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages adding and removal of cats
40. Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages the currently edited cat
55. • Also referred to as Smart or Stateful Components
• Knows about State and how to access it
• Responsible for …
• Getting the data from the state
• Passing down data
• Dispatching actions
Component Types - Container
57. <md-grid-tile *ngFor="let cat of filteredCats$ | async">
<!--
* Cat Detail Card gets cats from the container.
* and emits events up for the container to handle.
-->
<app-cat-detail-card
[cat]="cat"
(displayEdit)="displayCat($event);"
(deleteCat)="deleteCat($event)">
</app-cat-detail-card>
</md-grid-tile>
Container Component
58. <div class="sm-col sm-col-4”>
<b>All Cats</b>
<!-- Short List is dumb -->
<app-cat-short-list
[cats]="cats$"
[breeds]="catBreeds$">
</app-cat-short-list>
</div>
<div class="sm-col sm-col-4”>
<b>Filtered Cats</b>
<!-- Short List doesn't care where the cats came from,
just that it has them -->
<app-cat-short-list
[cats]="filteredCats$"
[breeds]="catBreeds$">
</app-cat-short-list>
</div>
Container Component
59. • Also referred to as Dumb or Stateless Components
• Receives data from containers
• Emits events up to parent
• Do not rely on external state that is not provided
• Do not modify external state
Presentation Component
60. Presentation Component
• Receives data from container or smart component.
@Input() cat: ICat;
• Emits events up to the parent
@Output() deleteCat: EventEmitter<ICat> = new EventEmitter();
• Responsible for …
• Rendering data.
• Emitting events.
64. Accessing State
• Can access underlying Redux methods.
• getState - snapshot of current state.
• subscribe - callback for every state change.
• Can use ngRedux `select` slices of state as an Observable.
• @select() cats$: Observable<ICat[]>;
• this.cats$ = ngRedux.select(state=>cats);
66. ActionServices
• Injectable Angular 2 Classes.
• Access to Angular DI.
• Dispatch from the service.
• Class methods are similar to Action Creators.
67. ActionServices
• Actions still pass through middleware.
• Call dispatch from the service.
• Action is still a JSON object that gets passed into your reducers
69. 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.
72. Observable State
@select() cats$: Observable<ICat[]>;
Remember this?
Its an RxJs Observable
map filtermergeMap
debounceTime groupBycombineLatest
…. and more
combine with other streams
73. Can use | async
<app-cat-edit-form
[cat]="catEdit$ | async"
[catBreeds]="catBreeds$ | async"
[catAges]="catAges$ | async"
[catGenders]="catGenders$ | async"
*ngIf="isEditing$ | async"
(submitCat)="submitCat($event)">
</app-cat-edit-form>
export class AppComponent implements OnInit {
@select(['catEdit', 'currentCat']) catEdit$: Observable<any>;
@select(['codeTables', 'genders']) catGenders$;
}
Async pipe can handle subscriptions and dispose for you
75. Transform State
• Functions passed into select are called every state change
• Transformations should happen only
when the state you care about has changed
76. Transform State
let greeter = p => `Hello ${p.firstName} ${p.lastName}`
export class AppComponent implements OnInit {
@select(state=>greeter(state.people)) greetings$: Observable<string[]>;
greetings: string[]
}
greeter gets executed every state change!
77. Transform State
let greeter = p => `Hello ${p.firstName} ${p.lastName}`
export class AppComponent implements OnInit {
@select() people$: Observable<IPerson[]>;
greetings$: Observable<string[]>
ngOnInit() {
this.greetings$ = this.people$
.map(greeter)
}
}
greeter gets executed only if people$ changes
79. Reducer: “How does my state change over time”?
: “How do other modules make sense of
my state?”
- @dan_abramov
“
Selectors
80. • Containers can be smart, but not too smart.
• Risk tightly coupling components to the shape of your state.
• Can make refactoring difficult.
Selectors
81. Selectors - Selecting State
export class Comp2 {
@select([‘path','to','expenses'])
expenses$:Observable<IExpenses[]>
}
• Changes in state can lead to updating multiple locations.
• Brittle, error prone.
• Makes refactoring risky.
export class Comp1 {
@select([‘path','to','expenses'])
expenses$:Observable<IExpenses[]>
}
82. Selectors - Selecting State
import { expenses } from './store';
export class Comp1 {
@select(expenses) expenses$:any
}
import { expenses } from './store';
export class Comp2 {
@select(expenses) expenses$:any
}
• Create a shared selector.
// colocated with the reducer
export const expenses = ['new','expense','path']
• Import and use.
83. Selectors - Transforming State
• Shape of state may not meet view needs.
• Can transform state.
• Can be used to derive data from existing state.
• Want to keep code DRY.
• Minimize logic in components.
95. • Focus on business / data logic.
• No longer concerned with coordinating async streams.
• Can begin to behave synchronously again.
Action Creators
96. • Focus on Async behaviour.
• Handling side effects.
• Take Actions in.
• Emit Actions out.
Epics