Redux + RxJS + Ember makes simple

38
Redux + RxJS + Ember Justin Park

Transcript of Redux + RxJS + Ember makes simple

Page 1: Redux + RxJS + Ember makes simple

Redux + RxJS + Ember

Justin Park

Page 2: Redux + RxJS + Ember makes simple

Managing state stuff is hard

• Specially in Ember• Controller/Router/Component/

ChildComponent

Page 3: Redux + RxJS + Ember makes simple

Ember.Object is painful

Page 4: Redux + RxJS + Ember makes simple

{{search-pill lastDays=lastDays}}

{{advanced-search lastDays=lastDays…}}

Page 5: Redux + RxJS + Ember makes simple

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

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

Page 6: Redux + RxJS + Ember makes simple

{{search-pill lastDays=lastDays}}

{{advanced-search lastDays=lastDays…}}

Page 7: Redux + RxJS + Ember makes simple

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

Page 8: Redux + RxJS + Ember makes simple

• 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)) …}

Page 9: Redux + RxJS + Ember makes simple

Data Down Action Up

Page 10: Redux + RxJS + Ember makes simple

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

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

Page 11: Redux + RxJS + Ember makes simple

Redux makes it simple

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

Page 12: Redux + RxJS + Ember makes simple

What is redux?• Provides predicable state

management using actions and reducers

Page 13: Redux + RxJS + Ember makes simple

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’}

Page 14: Redux + RxJS + Ember makes simple

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

previous state and an action and returns the new state

Page 15: Redux + RxJS + Ember makes simple

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

state(prevState, action) => state

Page 16: Redux + RxJS + Ember makes simple

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

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

Page 17: Redux + RxJS + Ember makes simple

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,});

Page 18: Redux + RxJS + Ember makes simple

How can I connect?

• Container component• Presentation component

Page 19: Redux + RxJS + Ember makes simple

Container Component• ember-redux/components/connect

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

Page 20: Redux + RxJS + Ember makes simple

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}}

Page 21: Redux + RxJS + Ember makes simple

Managing async stuff is harder

Page 22: Redux + RxJS + Ember makes simple

RxJS makes it manageable

Page 23: Redux + RxJS + Ember makes simple

What are async stuff do we commonly do?

Page 24: Redux + RxJS + Ember makes simple

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

Page 25: Redux + RxJS + Ember makes simple

THIS IS OPTIONALMAKE LIFE EASIER

Page 26: Redux + RxJS + Ember makes simple

Chains of actions

Page 27: Redux + RxJS + Ember makes simple

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…

Page 28: Redux + RxJS + Ember makes simple

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

Page 29: Redux + RxJS + Ember makes simple

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});),

Page 30: Redux + RxJS + Ember makes simple

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});),

Page 31: Redux + RxJS + Ember makes simple

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));}); …

Page 32: Redux + RxJS + Ember makes simple

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?

Page 33: Redux + RxJS + Ember makes simple

(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})) );

Page 34: Redux + RxJS + Ember makes simple

(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})) ) );

Page 35: Redux + RxJS + Ember makes simple

(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})) ) );

Page 36: Redux + RxJS + Ember makes simple

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})) ) );

Page 37: Redux + RxJS + Ember makes simple

(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())) );

Page 38: Redux + RxJS + Ember makes simple

Demo