Redux + RxJS + Ember makes simple
-
Upload
justin-park -
Category
Technology
-
view
92 -
download
1
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