Redux + RxJS + Ember makes simple

Post on 07-Feb-2017

92 views 1 download

Transcript of Redux + RxJS + Ember makes simple

Redux + RxJS + Ember

Justin Park

Managing state stuff is hard

• Specially in Ember• Controller/Router/Component/

ChildComponent

Ember.Object is painful

{{search-pill lastDays=lastDays}}

{{advanced-search lastDays=lastDays…}}

this.set(‘lastDays’, ‘’);

Const MIN_VALUE = 1;Ember.observer(‘lastDays’, ()=> { if (this.get(‘lastDays’) < MIN_VALUE) this.set(‘lastDays’, MIN_VALUE);});

{{search-pill lastDays=lastDays}}

{{advanced-search lastDays=lastDays…}}

Too much!• addObserver• cacheFor• notifyPropertyChange• removeObserver• …

• this.setProperties(‘attr1’, { value1: 1, arrKey: [1, 2, {objKey1: {…}}]});

• {{child-component attr1=attr1}}• func1(newItem) {

const arr= this.get(‘attr1.arrKey’); arr.addObject(newItem) // ? or arr.push(newItem) // ? Or this.set(‘attr1.arrKey’,Ember.A(arr).addObject(newItem)) …}

Data Down Action Up

{{my-input value=(readonly my.prop) on-change=(action (mut my.prop))}}

• http://emberup.co/bindings-with-htmlbars-helpers/

Redux makes it simple

• Your app is stored in an object tree inside a single store.

What is redux?• Provides predicable state

management using actions and reducers

What’s an “action”?• Describes something has happen, but they don’t

specify how is should be done• { type: ‘ADD_MSG’, payload: { msg: ‘New

Message’}

What’s a “reducer”?• A pure function that takes the

previous state and an action and returns the new state

What’s a “reducer”?• Sometimes it returns the previous

state(prevState, action) => state

What’s a “reducer”?• Sometimes it computes new state

(state, action) => state + action.payload

Each reducer is a separate objectindex.jsimport alert from './alert';import search from './search';import searchParams from './search-params';import savedSearch from './saved-search';import router from './router';

Export combineReducers({ alert, search, searchParams, savedSearch, router,});

How can I connect?

• Container component• Presentation component

Container Component• ember-redux/components/connect

• select(store): read• actions(dispatch): update

Container Component Template• {{yield msgType message isHide}}

(store) => {type: store.alert.type, msg:store.alert.msg, isHide:store.alert.isHide} {{containers.alert as |type msg isHide|}} {{alert-message type=type msg=msg isHide=(readonly isHide)}}{{/containers.alert}}

• {{yield alert=alert}}(store) => store.alert{{containers.alert as |alert|}} {{alert-message type=alert.type msg=alert.msg isHide=(readonly alert.isHide)}}{{/containers.alert}}

• {{yield (hash msgType=msgType message=message…)}}(store) => {...store.alert} or return Object.assign({}, store.alert);{{containers.alert as |alert|}} {{alert-message type=alert.type msg=alert.msg isHide=(readonly alert.isHide)}}{{/containers.alert}}

Managing async stuff is harder

RxJS makes it manageable

What are async stuff do we commonly do?

Async• User interactions (mouse, keyboard, etc)• AJAX• Timer/Animations• Workers, etc

THIS IS OPTIONALMAKE LIFE EASIER

Chains of actions

Scenario of fetching search result

• Loading• Load the records on success• Display an error message on fail• Abort the previous ajax call on new

request• Ignore the duplicate requests• Subsequence ajax calls, etc…

redux.dispatch({type: LOADING});this.store.read(‘search’).then(response => redux.dispatch({type: LOAD, payload: response});).fail(error=> redux.dispatch({type: ERROR, payload:error});),

redux.dispatch({type: LOADING});if (this.xhr) { this.xhr.abort(); }this.xhr = this.store.read(‘search’).then(response => redux.dispatch({type: LOAD, payload: response});).fail(error=> redux.dispatch({type: ERROR, payload:error});),

if (shallowEqual(this._preDataParams,dataParams)) return;redux.dispatch({type: LOADING});if (this.xhr) { this.xhr.abort(); }this._prevDataParams = dataParams;this.xhr = this.store.read(‘search’, dataParams).then(response => redux.dispatch({type: LOAD, payload: response});).fail(error=> redux.dispatch({type: ERROR, payload:error});),

if (shallowEqual(this._preDataParams,dataParams)) return;redux.dispatch({type: LOADING});if (this.xhr) { this.xhr.abort(); }this._prevDataParams = dataParams;this.xhr = this.store.read(‘search’, dataParams).then(response => redux.dispatch({type: LOAD, payload: response});).fail(error=> redux.dispatch({type: ERROR, payload:error});),this.xhr.then(response => { return response.items.map(item => this.store.read(‘search-exp’, item.id));}); …

if (shallowEqual(this._preDataParams,dataParams)) return;redux.dispatch({type: LOADING});if (this.xhr) { this.xhr.abort(); }…this.xhr.then(response => { return response.items.map(item => this.store.read(‘search-exp’, item.id));}); …

How can abort these?

(action$) => action$.ofType(LOADING) .map(action => action.payload) .mergeMap(payload => trexServices.store.read(‘search’) .map(response => { type: LOAD, payload: data.response }) .catch(() => Observable.of({type: ERROR})) );

(action$) => action$.ofType(REQUEST_READ) .map(action => action.payload) .mergeMap(payload => Observable.merge( Observable.of({type: LOADING}), trexServices.store.read(‘search’) .map(ajaxRes => ajaxRes.response) .map(response => { type: LOAD, payload: response }) .catch(() => Observable.of({type: ERROR})) ) );

(action$) => action$.ofType(REQUEST_READ) .map(action => action.payload) . switchMap(payload => Observable.merge( Observable.of({type: LOADING}), trexServices.store.read(‘search’) .map(ajaxRes => ajaxRes.response) .map(response => { type: LOAD, payload: response }) .catch(() => Observable.of({type: ERROR})) ) );

const comparator = (objA, objB) => shallowEqual(objA.dataParams, objB.dataParams);(action$) => action$.ofType(REQUEST_READ) .map(action => action.payload) .distinctUntilChanged(comparator) .switchMap(payload => Observable.merge( Observable.of({type: LOADING}), trexServices.store.read(‘search’, payload.dataParams) .map(ajaxRes => ajaxRes.response) .map(response => { type: LOAD, payload: response }) .catch(() => Observable.of({type: ERROR})) ) );

(action$) => action$.ofType(LOAD) .filter(([items]) => items.id) .switchMap(([source, items]) => Observable .mergeMap(() => Observable.merge( ...items.map(item=> trexServices.store.read(‘search-exp’, item.id) .map({type: LOAD_EXP, payload => response) ) )) .catch(() => Observable.of(Actions.requestFail())) );

Demo