Redux + RxJS + Ember
Justin Park
Managing state stuff is hard
• Specially in Ember
• Controller/Router/Component/ChildCompone
nt
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 object
index.js
import 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 ComponentTemplate
• {{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 OPTIONAL
MAKE 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

Redux + RxJS + Ember makes simple