SlideShare uma empresa Scribd logo
1 de 104
Baixar para ler offline
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.
• Managing state becomes a challenge.
How State Really Becomes Painful
Component Model
Model
Model
Model
Component
Component
Component
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.
Unidirectional Data Flow with Flux
Component
Store
API, etc.
Component
Dispatcher
Action(s)Action

Creator
Managing State with Redux
What is Redux?
Redux is a predictable state
container for JavaScript apps.
– @dan_ambrov
“
Redux Resources
• GitHub: https://github.com/reactjs/redux
• Docs: http://redux.js.org/
• Ecosystem: https://github.com/xgrommx/awesome-redux
• Training: https://egghead.io/courses/getting-started-with-redux
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.
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
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 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;
}
};
Testing Is Easy
describe('the cats reducer', () => {
it('should allow cats to be created', () => {
const initialState = cats([]);
const expectedState = [/* ... */];
const action = {
type:’CAT_CREATED',
payload: { /* ... */ }
};
const nextState = todos(initialState, action);
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.
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
Example Action
let action = {
type: ‘FULL_NAME_FORMED’,
payload: {
fullName: 'Evan Schultz'
}
}
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.
ActionCreator / Action
let formFullName = (firstName, lastName) => {
return {
type: ‘FULL_NAME_FORMED’,
payload: {
fullName: `${firstName} ${lastName}`
}
}
}
Putting it together …
• Action Creators are the request to do something.
• Actions are the result of what happened.
• Reducers transform to represent what happened.
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 one library to use.
var newData = data.setIn(
['foo', 'bar', 'baz'],
42
);
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);
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
• Angular 2 bindings for Redux.
• https://github.com/angular-redux/ng2-redux
• Expose state as an Observable.
• Property Decorators
• DevTools
• Compatible with existing Redux ecosystem.
• https://github.com/xgrommx/awesome-redux
Angular 2 + ng2-redux Setup
• Creating the Store - Root Reducer & Reducer Composition.
• Root Module Configuration.
• Component Architecture.
• Accessing State.
• Action Services.
• Middleware and DevTools.
Shelter Application
Store Configuration
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.
Create Root Reducer
import { combineReducers } from 'redux';
import { cats, ICat } from './cats';
import { catEdit, ICatEdit } from './cat-edit';
import { codeTables, ICodeTables } from './code-tables';
import { filters, IFilters } from './filters';
export interface IAppState {
cats?: ICat[];
catEdit?: ICatEdit;
codeTables?: ICodeTables;
filters?: IFilters;
};
export const rootReducer = combineReducers<IAppState>
({ cats, catEdit, codeTables, filters });
Create Root Reducer
import { combineReducers } from 'redux';
import { cats, ICat } from './cats';
import { catEdit, ICatEdit } from './cat-edit';
import { codeTables, ICodeTables } from './code-tables';
import { filters, IFilters } from './filters';
export interface IAppState {
cats?: ICat[];
catEdit?: ICatEdit;
codeTables?: ICodeTables;
filters?: IFilters;
};
export const rootReducer = combineReducers<IAppState>
({ cats, catEdit, codeTables, filters });
Create Root Reducer
import { combineReducers } from 'redux';
import { cats, ICat } from './cats';
import { catEdit, ICatEdit } from './cat-edit';
import { codeTables, ICodeTables } from './code-tables';
import { filters, IFilters } from './filters';
export interface IAppState {
cats?: ICat[];
catEdit?: ICatEdit;
codeTables?: ICodeTables;
filters?: IFilters;
};
export const rootReducer = combineReducers<IAppState>
({ cats, catEdit, codeTables, filters });
Resulting State …
resultingState = {
"cats": [{ "age": "senior", "breed": "munchkin", "description": "...",
/* .... */
}],
"catEdit": { "isEditing": false, "currentCat": null, "isPending": false
},
"codeTables": {
"ages": [{ "value": "senior", "label": "Senior" }],
"breeds": [{ "value": "munchkin", "label": "Munchkin"}]
"genders": [/* ...*/ ]
},
"filters": {
"age": {},
"breed": {},
"gender": {}
},
"catsLoading": false
}
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.
Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages adding and removal of cats
Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages the currently edited cat
Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages list tables like
•Breeds
•Age
•Gender
Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Manages what filters are applied
Combine Reducers
export const rootReducer =
combineReducers<IAppState>({
});
cats,
catEdit,
codeTables,
filters,
catsLoading
Is the cats list loading?
Nested CombineReducers
export const rootReducer =
combineReducers<IAppState>({
});
import { ages } from './ages.reducer';
import { breeds } from './breeds.reducer';
import { genders } from './genders.reducer';
import { combineReducers } from 'redux';
export const codeTables = combineReducers({ages, breeds, genders});
app/store/code-tables/code-tables.reducer.ts
codeTables,
Higher Order Reducers
export const rootReducer =
combineReducers<IAppState>({
});
filters,
const filterReducer = (property, INITIAL_STATE = {}) =>
(state = INITIAL_STATE, action) => {
/* .... */
switch (action.type) {
case FilterActions.FILTER_ADDED:
return Object.assign({}, state, { [action.payload.value]: true });
/* ... */
default:
return state;
}};
app/store/filters/filters.reducer.ts
Higher Order Reducers
export const rootReducer =
combineReducers<IAppState>({
});
filters,
export const filters = combineReducers({
age: filterReducer('age'),
breed: filterReducer('breed'),
gender: filterReducer('gender')
});
app/store/filters/filters.reducer.ts
Can be really … really small
export const rootReducer =
combineReducers<IAppState>({
});
export const catsLoading = (state = false, action) => {
switch (action.type) {
case CatsActions.CATS_LOADING:
return true;
case CatsActions.CATS_LOADED:
return false;
default:
return state;
}};
app/store/cats/cats-loading.reducer.ts
catsLoading
Whats the point?
• Can be built in many ways.
• Can be nested.
• Can be combined.
• Can be composed.
• Can be reusable.
Reducers …
NG2 Configuration
Angular 2 - Root Module
import { NgReduxModule, NgRedux } from 'ng2-redux';
import { rootReducer, IAppState } from './store';
@NgModule({
declarations: [ AppComponent ],
imports: [ NgReduxModule ],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private ngRedux: NgRedux<IAppState>) {
ngRedux.configureStore(rootReducer, {});
}
}
Angular 2 - Bootstrap
import './polyfills.ts';
import { platformBrowserDynamic } from '@angular/platform-
browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule } from './app/';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
Angular 2 - Component
export class AppComponent implements OnInit {
@select() cats$: Observable<ICat[]>;
constructor(private ngRedux:NgRedux<IAppState>) { }
createCat(catDetails) {
this.ngRedux.dispatch({type: 'CREAT_CAT',
payload: catDetails
});
}
}
Component Types
• 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
export class AppComponent implements OnInit {
/* Access State */
@select() cats$: Observable<ICat[]>;
constructor(private catsActions: CatsActions) { }
/* Dispatch Actions */
displayCat(cat) { this.ngRedux.dispatch({}); }
/* ActionService of CatActions deals with Dispatch */
submitCat(catModel) { this.catsActions.submitCat(catModel); }
}
Container Component
<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
<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
• 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
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.
Presentation Component
@Component({
selector: ‘ ',
templateUrl: './cat-detail-card.component.html',
styleUrls: ['./cat-detail-card.component.scss']
})
export class CatDetailCardComponent {
@Input() cat: any;
@Output() displayEdit: EventEmitter<string> = /* ... */
@Output() deleteCat: EventEmitter<any> = /* ... */
}
app-cat-detail-card
Presentation Template
<md-card>
<md-card-title-group>
<md-card-subtitle>{{cat.headline}}</md-card-subtitle>
<md-card-title>{{cat.name}}</md-card-title>
<img md-card-sm-image [src]="cat.imageUrl" />
</md-card-title-group>
<md-card-content>
<div [innerHtml]="cat.description"></div>
</md-card-content>
<md-card-actions>
<button md-button (click)="displayEdit.emit(cat)">EDIT</button>
<button md-button (click)="deleteCat.emit(cat)">DELETE</button>
</md-card-actions>
</md-card>
app-cat-detail-card
State and Actions
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);
Accessing State - @select
export class AppComponent {
@select() cats$: Observable<ICat[]>;
@select(['codeTables', 'breeds']) catBreeds$;
@select(['codeTables', 'ages']) catAges$;
@select((state)=>state.codeTables.genders) catGenders$;
}
ActionServices
• Injectable Angular 2 Classes.
• Access to Angular DI.
• Dispatch from the service.
• Class methods are similar to Action Creators.
ActionServices
• Actions still pass through middleware.
• Call dispatch from the service.
• Action is still a JSON object that gets passed into your reducers
Angular 2 - Synchronous Actions
@Injectable()
export class CatsActions {
static CAT_CREATED = 'CAT_CREATED';
constructor(private ngRedux: NgRedux<IAppState>) { };
createCat = ({name, headline, /* … snip … */ }) => {
const randomImage = randomRange(100, 200);
const imageUrl = /* … snip … */
const id = generateId();
const cat = { name, headline,/* … snip … */}
this.ngRedux
.dispatch({ type: CatsActions.CAT_CREATED, payload: cat });
};
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
listAll = () => {
this.ngRedux.dispatch({type: 'CATS_LOADING' }))
return this.cats
.listAll()
.subscribe(result => {
this.ngRedux.dispatch({
type: 'CATS_LOADED',
payload: result
});
},
(err) => this.ngRedux.dispatch({type: 'CATS_LOADING_ERROR'})
);
};
State is Observable
Observable State
@select() cats$: Observable<ICat[]>;
Remember this?
Its an RxJs Observable
map filtermergeMap
debounceTime groupBycombineLatest
…. and more
combine with other streams
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
Combining Streams
export class AppComponent implements OnInit {
@select() cats$: Observable<ICat[]>;
@select() filters$;
public filteredCats$;
ngOnInit() {
this.filteredCats$ = this.cats$
.combineLatest(this.filters$.map(activeFilters),
(cats, filters: any) => cats.filter(filters));
}
Transform State
• Functions passed into select are called every state change
• Transformations should happen only

when the state you care about has changed
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!
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
Selectors
(*) or how to decouple state structure from
components
Reducer: “How does my state change over time”?
: “How do other modules make sense of
my state?”
- @dan_abramov
“
Selectors
• Containers can be smart, but not too smart.
• Risk tightly coupling components to the shape of your state.
• Can make refactoring difficult.
Selectors
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[]>
}
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.
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.
Selectors - Transforming State
export expenses = ['path','to','expenses'];
export const approvedTotal = (expenses) => {
return expenses
.filter( n=> n.approved)
.reduce((acc,curExpense) => acc + curExpense.total,0)
}
Selectors - Transforming State
import { approvedTotal, expenses } from './store';
@Component({ /* ... */ })
export class ExpenseComponent {
total$:Observable<string>
ngOnInit() {
this.total$ = this.ngRedux
.select(expenses)
.map(approvedTotal)
}
}
Developer Experience
• See all actions dispatched.
• See state before action.
• See state after action.
Logger Middleware
Logger - Setup
const createLogger = require('redux-logger');
const logger = createLogger({level:'info', collapse: true })
export const middleware = [logger];
app/store/store.ts
import { rootReducer, IAppState, middleware } from './store';
@NgModule({/* ....*/ })
export class AppModule {
constructor(private ngRedux: NgRedux<IAppState>) {
ngRedux.configureStore(rootReducer, {}, [...middleware] );
}
}
app/app.module.ts
Logger - Output
• Works with Redux Dev Tools.
• Available in Chrome Store - [Link].
• Action history.
• Time traveling.
• State Import & Export.
Redux Dev Tools
DevTools - Setup
import { NgReduxModule,
NgRedux,
DevToolsExtension } from 'ng2-redux';
@NgModule({/* ...*/ })
export class AppModule {
constructor(private ngRedux: NgRedux<IAppState>,
devTools: DevToolsExtension) {
let enhancers = devTools.isEnabled() ?
[ devTools.enhancer() ] : [];
ngRedux.configureStore(rootReducer,
{ },
[...middleware],
[...enhancers]);
}
}
app/app.module.ts
Epics
• Observe an Action stream as Observable.
• Handle complex workflows.
• Cancellable.
• Simplify action creators.
Middleware - redux-observable
• Focus on business / data logic.
• No longer concerned with coordinating async streams.
• Can begin to behave synchronously again.
Action Creators
• Focus on Async behaviour.
• Handling side effects.
• Take Actions in.
• Emit Actions out.
Epics
Create Cat - Epic
@Injectable()
export class CatEpics {
/* ... */
create = (action$: ActionsObservable<IPayloadAction>) => {
let dispatch = this.dispatch;
let errorHandler = createErrorHandler(CatsActions.CAT_CREATE_ERROR);
let catCreated = createSuccessHandler(CatsActions.CAT_CREATED);
let createCat = ({payload}) => this.cats.create(payload)
.map(result => catCreated(result))
.catch(err => errorHandler(err));
return action$.ofType(CatsActions.CREATE_CAT)
.do(n => dispatch({ type: CatsActions.CREATING_CAT }))
.mergeMap(n => createCat(n));
}
}
Create Cat - Register Epic
import { rootReducer, IAppState, middleware, CatEpics } from './store';
import { createEpicMiddleware } from 'redux-observable';
export class AppModule {
constructor(private ngRedux: NgRedux<IAppState>,
devTools: DevToolsExtension,
catEpics: CatEpics) {
const createCatEpic = createEpicMiddleware(catEpics.create);
const updateCatEpic = createEpicMiddleware(catEpics.update);
let enhancers = devTools.isEnabled() ? [ devTools.enhancer() ] : [];
ngRedux.configureStore(rootReducer,
{},
[...middleware, createCatEpic, updateCatEpic],
[...enhancers]);
}
}
Create Cat - Action Creator
createCat = ({name, headline, description, age, gender, breed}) => {
const randomImage = randomRange(100, 200);
const imageUrl = `https://placekitten.com/${randomImage}/${randomImage}`;
const id = generateId();
this.ngRedux.dispatch({type: CatsActions.CREATING_CAT});
this.cats.create({ /* .... */ })
.subscribe(result => this.ngRedux.dispatch({
type: CatsActions.CAT_CREATED,
payload: result })
,
err => this.ngRedux.dispatch({
type: CatsActions.CAT_CREATE_ERROR,
payload: err})
);
};
Before
Create Cat - Action Creator
createCat = ({name, headline, description, age, gender, breed}) => {
const randomImage = randomRange(100, 200);
const imageUrl = `https://placekitten.com/${randomImage}/${randomImage}`;
const id = generateId();
this.ngRedux.dispatch({
type: CatsActions.CREATE_CAT,
payload: {
/* .... */
}
});
After
Demo Time
Demo
• Summit Cat Shelter
• https://github.com/e-schultz/ng-summit-2016-shelter
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

Mais conteúdo relacionado

Mais procurados (20)

Introduction to React & Redux
Introduction to React & ReduxIntroduction to React & Redux
Introduction to React & Redux
 
Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018
 
Introduction to ReactJS and Redux
Introduction to ReactJS and ReduxIntroduction to ReactJS and Redux
Introduction to ReactJS and Redux
 
Boosting Angular runtime performance
Boosting Angular runtime performanceBoosting Angular runtime performance
Boosting Angular runtime performance
 
React & Redux
React & ReduxReact & Redux
React & Redux
 
How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!
 
Better React state management with Redux
Better React state management with ReduxBetter React state management with Redux
Better React state management with Redux
 
Let's Redux!
Let's Redux!Let's Redux!
Let's Redux!
 
Redux in Angular2 for jsbe
Redux in Angular2 for jsbeRedux in Angular2 for jsbe
Redux in Angular2 for jsbe
 
Deep Dive into React Hooks
Deep Dive into React HooksDeep Dive into React Hooks
Deep Dive into React Hooks
 
React redux
React reduxReact redux
React redux
 
React with Redux
React with ReduxReact with Redux
React with Redux
 
State Models for React with Redux
State Models for React with ReduxState Models for React with Redux
State Models for React with Redux
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
 
Redux training
Redux trainingRedux training
Redux training
 
Ngrx slides
Ngrx slidesNgrx slides
Ngrx slides
 
React lecture
React lectureReact lecture
React lecture
 
React&redux
React&reduxReact&redux
React&redux
 
Ngrx
NgrxNgrx
Ngrx
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 

Destaque

HotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushHotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushEvan Schultz
 
Mobile apps with Ionic 2
Mobile apps with Ionic 2Mobile apps with Ionic 2
Mobile apps with Ionic 2Khoa Nguyễn
 
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...La Drupalera
 
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...La Drupalera
 
Angular 2 Campus Madrid Septiembre 2016
Angular 2 Campus Madrid Septiembre 2016Angular 2 Campus Madrid Septiembre 2016
Angular 2 Campus Madrid Septiembre 2016Micael Gallego
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training Patrick Schroeder
 
Ionic 2: Mobile apps with the Web
Ionic 2: Mobile apps with the WebIonic 2: Mobile apps with the Web
Ionic 2: Mobile apps with the WebMike Hartington
 
Ionic 2: The Power of TypeScript
Ionic 2:  The Power of TypeScriptIonic 2:  The Power of TypeScript
Ionic 2: The Power of TypeScriptJacob Orshalick
 
Ionic 2 como ferramenta para desenvolvimento móvel
Ionic 2 como ferramenta para desenvolvimento móvelIonic 2 como ferramenta para desenvolvimento móvel
Ionic 2 como ferramenta para desenvolvimento móvelGustavo Costa
 
Building Universal Applications with Angular 2
Building Universal Applications with Angular 2Building Universal Applications with Angular 2
Building Universal Applications with Angular 2Minko Gechev
 
How to Make Awesome SlideShares: Tips & Tricks
How to Make Awesome SlideShares: Tips & TricksHow to Make Awesome SlideShares: Tips & Tricks
How to Make Awesome SlideShares: Tips & TricksSlideShare
 
Getting Started With SlideShare
Getting Started With SlideShareGetting Started With SlideShare
Getting Started With SlideShareSlideShare
 

Destaque (16)

HotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushHotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePush
 
Gdg ionic 2
Gdg ionic 2Gdg ionic 2
Gdg ionic 2
 
Mobile apps with Ionic 2
Mobile apps with Ionic 2Mobile apps with Ionic 2
Mobile apps with Ionic 2
 
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...
Docker, your best ally to migrate & upgrading your Drupal - Drupal Dev Days S...
 
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...
Beyond the web: Mobile apps using Drupal & Ionic 2 - Drupal Dev Days Seville ...
 
Angular 2 - Better or worse
Angular 2 - Better or worseAngular 2 - Better or worse
Angular 2 - Better or worse
 
Angular 2 Campus Madrid Septiembre 2016
Angular 2 Campus Madrid Septiembre 2016Angular 2 Campus Madrid Septiembre 2016
Angular 2 Campus Madrid Septiembre 2016
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training
 
Ionic 2: Mobile apps with the Web
Ionic 2: Mobile apps with the WebIonic 2: Mobile apps with the Web
Ionic 2: Mobile apps with the Web
 
Ionic 2 - Introduction
Ionic 2 - IntroductionIonic 2 - Introduction
Ionic 2 - Introduction
 
Ionic 2: The Power of TypeScript
Ionic 2:  The Power of TypeScriptIonic 2:  The Power of TypeScript
Ionic 2: The Power of TypeScript
 
Intro to ionic 2
Intro to ionic 2Intro to ionic 2
Intro to ionic 2
 
Ionic 2 como ferramenta para desenvolvimento móvel
Ionic 2 como ferramenta para desenvolvimento móvelIonic 2 como ferramenta para desenvolvimento móvel
Ionic 2 como ferramenta para desenvolvimento móvel
 
Building Universal Applications with Angular 2
Building Universal Applications with Angular 2Building Universal Applications with Angular 2
Building Universal Applications with Angular 2
 
How to Make Awesome SlideShares: Tips & Tricks
How to Make Awesome SlideShares: Tips & TricksHow to Make Awesome SlideShares: Tips & Tricks
How to Make Awesome SlideShares: Tips & Tricks
 
Getting Started With SlideShare
Getting Started With SlideShareGetting Started With SlideShare
Getting Started With SlideShare
 

Semelhante a Evan Schultz - Angular Summit - 2016

Building React Applications with Redux
Building React Applications with ReduxBuilding React Applications with Redux
Building React Applications with ReduxFITC
 
[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation洪 鹏发
 
Workshop 19: ReactJS Introduction
Workshop 19: ReactJS IntroductionWorkshop 19: ReactJS Introduction
Workshop 19: ReactJS IntroductionVisual Engineering
 
Art of Javascript
Art of JavascriptArt of Javascript
Art of JavascriptTarek Yehia
 
Reactive state management with Jetpack Components
Reactive state management with Jetpack ComponentsReactive state management with Jetpack Components
Reactive state management with Jetpack ComponentsGabor Varadi
 
Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]Devon Bernard
 
Purifying React (with annotations)
Purifying React (with annotations)Purifying React (with annotations)
Purifying React (with annotations)Robin Pokorny
 
React && React Native workshop
React && React Native workshopReact && React Native workshop
React && React Native workshopStacy Goh
 
iOS Dev Happy Hour Realm - Feb 2021
iOS Dev Happy Hour Realm - Feb 2021iOS Dev Happy Hour Realm - Feb 2021
iOS Dev Happy Hour Realm - Feb 2021Jason Flax
 
Connect.Tech- Swift Memory Management
Connect.Tech- Swift Memory ManagementConnect.Tech- Swift Memory Management
Connect.Tech- Swift Memory Managementstable|kernel
 
Getting Reactive with CycleJS and XStream
Getting Reactive with CycleJS and XStream Getting Reactive with CycleJS and XStream
Getting Reactive with CycleJS and XStream TechExeter
 
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay PlatonovSenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay PlatonovSencha
 
React state managmenet with Redux
React state managmenet with ReduxReact state managmenet with Redux
React state managmenet with ReduxVedran Blaženka
 
JSLT: JSON querying and transformation
JSLT: JSON querying and transformationJSLT: JSON querying and transformation
JSLT: JSON querying and transformationLars Marius Garshol
 
Using React, Redux and Saga with Lottoland APIs
Using React, Redux and Saga with Lottoland APIsUsing React, Redux and Saga with Lottoland APIs
Using React, Redux and Saga with Lottoland APIsMihail Gaberov
 

Semelhante a Evan Schultz - Angular Summit - 2016 (20)

Building React Applications with Redux
Building React Applications with ReduxBuilding React Applications with Redux
Building React Applications with Redux
 
Redux js
Redux jsRedux js
Redux js
 
Understanding redux
Understanding reduxUnderstanding redux
Understanding redux
 
[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation
 
Workshop 19: ReactJS Introduction
Workshop 19: ReactJS IntroductionWorkshop 19: ReactJS Introduction
Workshop 19: ReactJS Introduction
 
Art of Javascript
Art of JavascriptArt of Javascript
Art of Javascript
 
Reactive state management with Jetpack Components
Reactive state management with Jetpack ComponentsReactive state management with Jetpack Components
Reactive state management with Jetpack Components
 
Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]
 
Purifying React (with annotations)
Purifying React (with annotations)Purifying React (with annotations)
Purifying React (with annotations)
 
React && React Native workshop
React && React Native workshopReact && React Native workshop
React && React Native workshop
 
iOS Dev Happy Hour Realm - Feb 2021
iOS Dev Happy Hour Realm - Feb 2021iOS Dev Happy Hour Realm - Feb 2021
iOS Dev Happy Hour Realm - Feb 2021
 
From * to Symfony2
From * to Symfony2From * to Symfony2
From * to Symfony2
 
Connect.Tech- Swift Memory Management
Connect.Tech- Swift Memory ManagementConnect.Tech- Swift Memory Management
Connect.Tech- Swift Memory Management
 
Getting Reactive with CycleJS and XStream
Getting Reactive with CycleJS and XStream Getting Reactive with CycleJS and XStream
Getting Reactive with CycleJS and XStream
 
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay PlatonovSenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov
SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov
 
cb streams - gavin pickin
cb streams - gavin pickincb streams - gavin pickin
cb streams - gavin pickin
 
React state managmenet with Redux
React state managmenet with ReduxReact state managmenet with Redux
React state managmenet with Redux
 
Redux tutorial - intro to Redux by GetLittleTech
Redux tutorial - intro to Redux by GetLittleTechRedux tutorial - intro to Redux by GetLittleTech
Redux tutorial - intro to Redux by GetLittleTech
 
JSLT: JSON querying and transformation
JSLT: JSON querying and transformationJSLT: JSON querying and transformation
JSLT: JSON querying and transformation
 
Using React, Redux and Saga with Lottoland APIs
Using React, Redux and Saga with Lottoland APIsUsing React, Redux and Saga with Lottoland APIs
Using React, Redux and Saga with Lottoland APIs
 

Evan Schultz - Angular Summit - 2016

  • 1. Evan Schultz Developer @Rangleio Some rights reserved - Creative Commons 2.0 by-sa BUILDING ANGULAR 2 APPLICATIONS WITH REDUX
  • 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
  • 11. Redux is a predictable state container for JavaScript apps. – @dan_ambrov “
  • 12. Redux Resources • GitHub: https://github.com/reactjs/redux • Docs: http://redux.js.org/ • Ecosystem: https://github.com/xgrommx/awesome-redux • Training: https://egghead.io/courses/getting-started-with-redux
  • 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; } };
  • 17. Testing Is Easy describe('the cats reducer', () => { it('should allow cats to be created', () => { const initialState = cats([]); const expectedState = [/* ... */]; const action = { type:’CAT_CREATED', payload: { /* ... */ } }; const nextState = todos(initialState, action); expect(nextState).to.deep.equal(expectedState); }); });
  • 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
  • 20. Example Action let action = { type: ‘FULL_NAME_FORMED’, payload: { fullName: 'Evan Schultz' } }
  • 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.
  • 22. ActionCreator / Action let formFullName = (firstName, lastName) => { return { type: ‘FULL_NAME_FORMED’, payload: { fullName: `${firstName} ${lastName}` } } }
  • 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.
  • 28. Angular 2 + Redux
  • 29. ng2-redux • Angular 2 bindings for Redux. • https://github.com/angular-redux/ng2-redux • Expose state as an Observable. • Property Decorators • DevTools • Compatible with existing Redux ecosystem. • https://github.com/xgrommx/awesome-redux
  • 30. Angular 2 + ng2-redux Setup • Creating the Store - Root Reducer & Reducer Composition. • Root Module Configuration. • Component Architecture. • Accessing State. • Action Services. • Middleware and DevTools.
  • 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.
  • 34. Create Root Reducer import { combineReducers } from 'redux'; import { cats, ICat } from './cats'; import { catEdit, ICatEdit } from './cat-edit'; import { codeTables, ICodeTables } from './code-tables'; import { filters, IFilters } from './filters'; export interface IAppState { cats?: ICat[]; catEdit?: ICatEdit; codeTables?: ICodeTables; filters?: IFilters; }; export const rootReducer = combineReducers<IAppState> ({ cats, catEdit, codeTables, filters });
  • 35. Create Root Reducer import { combineReducers } from 'redux'; import { cats, ICat } from './cats'; import { catEdit, ICatEdit } from './cat-edit'; import { codeTables, ICodeTables } from './code-tables'; import { filters, IFilters } from './filters'; export interface IAppState { cats?: ICat[]; catEdit?: ICatEdit; codeTables?: ICodeTables; filters?: IFilters; }; export const rootReducer = combineReducers<IAppState> ({ cats, catEdit, codeTables, filters });
  • 36. Create Root Reducer import { combineReducers } from 'redux'; import { cats, ICat } from './cats'; import { catEdit, ICatEdit } from './cat-edit'; import { codeTables, ICodeTables } from './code-tables'; import { filters, IFilters } from './filters'; export interface IAppState { cats?: ICat[]; catEdit?: ICatEdit; codeTables?: ICodeTables; filters?: IFilters; }; export const rootReducer = combineReducers<IAppState> ({ cats, catEdit, codeTables, filters });
  • 37. Resulting State … resultingState = { "cats": [{ "age": "senior", "breed": "munchkin", "description": "...", /* .... */ }], "catEdit": { "isEditing": false, "currentCat": null, "isPending": false }, "codeTables": { "ages": [{ "value": "senior", "label": "Senior" }], "breeds": [{ "value": "munchkin", "label": "Munchkin"}] "genders": [/* ...*/ ] }, "filters": { "age": {}, "breed": {}, "gender": {} }, "catsLoading": false }
  • 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
  • 41. Combine Reducers export const rootReducer = combineReducers<IAppState>({ }); cats, catEdit, codeTables, filters, catsLoading Manages list tables like •Breeds •Age •Gender
  • 42. Combine Reducers export const rootReducer = combineReducers<IAppState>({ }); cats, catEdit, codeTables, filters, catsLoading Manages what filters are applied
  • 43. Combine Reducers export const rootReducer = combineReducers<IAppState>({ }); cats, catEdit, codeTables, filters, catsLoading Is the cats list loading?
  • 44. Nested CombineReducers export const rootReducer = combineReducers<IAppState>({ }); import { ages } from './ages.reducer'; import { breeds } from './breeds.reducer'; import { genders } from './genders.reducer'; import { combineReducers } from 'redux'; export const codeTables = combineReducers({ages, breeds, genders}); app/store/code-tables/code-tables.reducer.ts codeTables,
  • 45. Higher Order Reducers export const rootReducer = combineReducers<IAppState>({ }); filters, const filterReducer = (property, INITIAL_STATE = {}) => (state = INITIAL_STATE, action) => { /* .... */ switch (action.type) { case FilterActions.FILTER_ADDED: return Object.assign({}, state, { [action.payload.value]: true }); /* ... */ default: return state; }}; app/store/filters/filters.reducer.ts
  • 46. Higher Order Reducers export const rootReducer = combineReducers<IAppState>({ }); filters, export const filters = combineReducers({ age: filterReducer('age'), breed: filterReducer('breed'), gender: filterReducer('gender') }); app/store/filters/filters.reducer.ts
  • 47. Can be really … really small export const rootReducer = combineReducers<IAppState>({ }); export const catsLoading = (state = false, action) => { switch (action.type) { case CatsActions.CATS_LOADING: return true; case CatsActions.CATS_LOADED: return false; default: return state; }}; app/store/cats/cats-loading.reducer.ts catsLoading
  • 49. • Can be built in many ways. • Can be nested. • Can be combined. • Can be composed. • Can be reusable. Reducers …
  • 51. Angular 2 - Root Module import { NgReduxModule, NgRedux } from 'ng2-redux'; import { rootReducer, IAppState } from './store'; @NgModule({ declarations: [ AppComponent ], imports: [ NgReduxModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { constructor(private ngRedux: NgRedux<IAppState>) { ngRedux.configureStore(rootReducer, {}); } }
  • 52. Angular 2 - Bootstrap import './polyfills.ts'; import { platformBrowserDynamic } from '@angular/platform- browser-dynamic'; import { enableProdMode } from '@angular/core'; import { environment } from './environments/environment'; import { AppModule } from './app/'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule);
  • 53. Angular 2 - Component export class AppComponent implements OnInit { @select() cats$: Observable<ICat[]>; constructor(private ngRedux:NgRedux<IAppState>) { } createCat(catDetails) { this.ngRedux.dispatch({type: 'CREAT_CAT', payload: catDetails }); } }
  • 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
  • 56. export class AppComponent implements OnInit { /* Access State */ @select() cats$: Observable<ICat[]>; constructor(private catsActions: CatsActions) { } /* Dispatch Actions */ displayCat(cat) { this.ngRedux.dispatch({}); } /* ActionService of CatActions deals with Dispatch */ submitCat(catModel) { this.catsActions.submitCat(catModel); } } Container Component
  • 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.
  • 61. Presentation Component @Component({ selector: ‘ ', templateUrl: './cat-detail-card.component.html', styleUrls: ['./cat-detail-card.component.scss'] }) export class CatDetailCardComponent { @Input() cat: any; @Output() displayEdit: EventEmitter<string> = /* ... */ @Output() deleteCat: EventEmitter<any> = /* ... */ } app-cat-detail-card
  • 62. Presentation Template <md-card> <md-card-title-group> <md-card-subtitle>{{cat.headline}}</md-card-subtitle> <md-card-title>{{cat.name}}</md-card-title> <img md-card-sm-image [src]="cat.imageUrl" /> </md-card-title-group> <md-card-content> <div [innerHtml]="cat.description"></div> </md-card-content> <md-card-actions> <button md-button (click)="displayEdit.emit(cat)">EDIT</button> <button md-button (click)="deleteCat.emit(cat)">DELETE</button> </md-card-actions> </md-card> app-cat-detail-card
  • 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);
  • 65. Accessing State - @select export class AppComponent { @select() cats$: Observable<ICat[]>; @select(['codeTables', 'breeds']) catBreeds$; @select(['codeTables', 'ages']) catAges$; @select((state)=>state.codeTables.genders) catGenders$; }
  • 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
  • 68. Angular 2 - Synchronous Actions @Injectable() export class CatsActions { static CAT_CREATED = 'CAT_CREATED'; constructor(private ngRedux: NgRedux<IAppState>) { }; createCat = ({name, headline, /* … snip … */ }) => { const randomImage = randomRange(100, 200); const imageUrl = /* … snip … */ const id = generateId(); const cat = { name, headline,/* … snip … */} this.ngRedux .dispatch({ type: CatsActions.CAT_CREATED, payload: cat }); };
  • 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.
  • 70. Angular 2 - Async Actions listAll = () => { this.ngRedux.dispatch({type: 'CATS_LOADING' })) return this.cats .listAll() .subscribe(result => { this.ngRedux.dispatch({ type: 'CATS_LOADED', payload: result }); }, (err) => this.ngRedux.dispatch({type: 'CATS_LOADING_ERROR'}) ); };
  • 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
  • 74. Combining Streams export class AppComponent implements OnInit { @select() cats$: Observable<ICat[]>; @select() filters$; public filteredCats$; ngOnInit() { this.filteredCats$ = this.cats$ .combineLatest(this.filters$.map(activeFilters), (cats, filters: any) => cats.filter(filters)); }
  • 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
  • 78. Selectors (*) or how to decouple state structure from components
  • 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.
  • 84. Selectors - Transforming State export expenses = ['path','to','expenses']; export const approvedTotal = (expenses) => { return expenses .filter( n=> n.approved) .reduce((acc,curExpense) => acc + curExpense.total,0) }
  • 85. Selectors - Transforming State import { approvedTotal, expenses } from './store'; @Component({ /* ... */ }) export class ExpenseComponent { total$:Observable<string> ngOnInit() { this.total$ = this.ngRedux .select(expenses) .map(approvedTotal) } }
  • 87. • See all actions dispatched. • See state before action. • See state after action. Logger Middleware
  • 88. Logger - Setup const createLogger = require('redux-logger'); const logger = createLogger({level:'info', collapse: true }) export const middleware = [logger]; app/store/store.ts import { rootReducer, IAppState, middleware } from './store'; @NgModule({/* ....*/ }) export class AppModule { constructor(private ngRedux: NgRedux<IAppState>) { ngRedux.configureStore(rootReducer, {}, [...middleware] ); } } app/app.module.ts
  • 90. • Works with Redux Dev Tools. • Available in Chrome Store - [Link]. • Action history. • Time traveling. • State Import & Export. Redux Dev Tools
  • 91. DevTools - Setup import { NgReduxModule, NgRedux, DevToolsExtension } from 'ng2-redux'; @NgModule({/* ...*/ }) export class AppModule { constructor(private ngRedux: NgRedux<IAppState>, devTools: DevToolsExtension) { let enhancers = devTools.isEnabled() ? [ devTools.enhancer() ] : []; ngRedux.configureStore(rootReducer, { }, [...middleware], [...enhancers]); } } app/app.module.ts
  • 92.
  • 93. Epics
  • 94. • Observe an Action stream as Observable. • Handle complex workflows. • Cancellable. • Simplify action creators. Middleware - redux-observable
  • 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
  • 97. Create Cat - Epic @Injectable() export class CatEpics { /* ... */ create = (action$: ActionsObservable<IPayloadAction>) => { let dispatch = this.dispatch; let errorHandler = createErrorHandler(CatsActions.CAT_CREATE_ERROR); let catCreated = createSuccessHandler(CatsActions.CAT_CREATED); let createCat = ({payload}) => this.cats.create(payload) .map(result => catCreated(result)) .catch(err => errorHandler(err)); return action$.ofType(CatsActions.CREATE_CAT) .do(n => dispatch({ type: CatsActions.CREATING_CAT })) .mergeMap(n => createCat(n)); } }
  • 98. Create Cat - Register Epic import { rootReducer, IAppState, middleware, CatEpics } from './store'; import { createEpicMiddleware } from 'redux-observable'; export class AppModule { constructor(private ngRedux: NgRedux<IAppState>, devTools: DevToolsExtension, catEpics: CatEpics) { const createCatEpic = createEpicMiddleware(catEpics.create); const updateCatEpic = createEpicMiddleware(catEpics.update); let enhancers = devTools.isEnabled() ? [ devTools.enhancer() ] : []; ngRedux.configureStore(rootReducer, {}, [...middleware, createCatEpic, updateCatEpic], [...enhancers]); } }
  • 99. Create Cat - Action Creator createCat = ({name, headline, description, age, gender, breed}) => { const randomImage = randomRange(100, 200); const imageUrl = `https://placekitten.com/${randomImage}/${randomImage}`; const id = generateId(); this.ngRedux.dispatch({type: CatsActions.CREATING_CAT}); this.cats.create({ /* .... */ }) .subscribe(result => this.ngRedux.dispatch({ type: CatsActions.CAT_CREATED, payload: result }) , err => this.ngRedux.dispatch({ type: CatsActions.CAT_CREATE_ERROR, payload: err}) ); }; Before
  • 100. Create Cat - Action Creator createCat = ({name, headline, description, age, gender, breed}) => { const randomImage = randomRange(100, 200); const imageUrl = `https://placekitten.com/${randomImage}/${randomImage}`; const id = generateId(); this.ngRedux.dispatch({ type: CatsActions.CREATE_CAT, payload: { /* .... */ } }); After
  • 102. Demo • Summit Cat Shelter • https://github.com/e-schultz/ng-summit-2016-shelter
  • 103. Caveats • Its addictive. • You won’t be happy using anything else. • Your friends might not understand your obsession.