Reputation: 51707
My app is using a websocket service based on ember-phoenix to push new records from the API to the store. I would like these new records to render in my template when they're added.
I have a route where the model hook returns a filtered query promise:
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', {filter: {date: params.date}})
},
afterModel() {
get(this, 'socket').joinSchedule();
},
resetController() {
get(this, 'socket').leaveSchedule();
},
});
When new records are pushed to the store through the websocket, they are not rendered by my template because of how store.query
works. If I change store.query
to store.findAll
the new records are rendered, but I want my route to only load a subset of all the records based on the date query param.
It seems like my only option is to just reload the route's model when a new record is pushed to the store. Is it possible to do this from the service? If not, is there a different approach I might want to explore?
The relevant parts of my socket service are below:
import Ember from 'ember';
import PhoenixSocket from 'phoenix/services/phoenix-socket';
const {
get,
inject,
} = Ember;
export default PhoenixSocket.extend({
session: inject.service(),
store: inject.service(),
joinSchedule() {
const channel = this.joinChannel(`v1:my-model`);
channel.on('sync', (payload) => this._handleSync(payload));
},
_handleSync(payload) {
get(this, 'store').pushPayload(payload);
},
});
Upvotes: 4
Views: 595
Reputation: 12872
Option 1
You can use Ember.Evented to subscribe and dispatch event. I have created twiddle for demonstration.
In socket
service,
socket
should extend Ember.Evented
class
export default PhoenixSocket.extend(Ember.Evented, {
After updating store, you can just trigger myModelDataLoaded
which will dispatch all the functions subscribed to myModelDataLoaded
.
_handleSync(payload) { get(this, 'store').pushPayload(payload); this.trigger('myModelDataLoaded'); //this will call the functions subscribed to myModelDataLoaded. }
In Route,
myModelDataLoaded
afterModel() { get(this, 'socket').joinSchedule(); get(this, 'socket').on('myModelDataLoaded', this, this.refreshRoute); //we are subscribing to myModelDataLoaded }
Define refreshRoute function and call refresh
function.
refreshRoute() { this.refresh(); //forcing this route to refresh }
off
subscribtion, you can do it either in resetController
or deactivate
hook.
resetController() { get(this, 'socket').leaveSchedule(); get(this, 'socket').off('myModelDataLoaded', this, this.refreshRoute); }
Option 2.
You can watch store using peekAll with observer and refresh route.
In your controller,
1. Define postModel
computed property which will return live record array.
2. Define postModelObserver
dependant on postModel.[]
this will ensure whenever store is updated with new row, it will be observed by myModelObserver
and it will send action refreshRoute
to route . where we will call refresh
. As you know this will call beforeModel
, model
, afterModel
method.
As you know computed property is lazy, when you are accessing it only then it will be computed. so if you are not using it in template, then just add this.get('myModel')
in init
method
Controller file
import Ember from 'ember';
const { computed } = Ember;
export default Ember.Controller.extend({
init() {
this._super(...arguments);
this.get('postModel');//this is just to trigger myModel computed property
},
postModel: computed(function() {
return this.get('store').peekAll('post');
}),
postModelObserver: Ember.observer('postModel.[]', function() {
this.send('refreshRoute');
})
});
Route file - define action refreshRoute
for refreshing, since refresh
is available only in route.
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', { filter: { date: params.date } })
},
afterModel() {
get(this, 'socket').joinSchedule();
},
resetController() {
get(this, 'socket').leaveSchedule();
},
actions:{
refreshRoute() {
this.refresh();
},
}
});
Upvotes: 3
Reputation: 1858
It is not a better way, but one way to do with your existing code is using a callback.
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', {filter: {date: params.date}})
},
afterModel() {
let cb = (myModelRecord) => {
this.get('model').push(myModelRecord);
};
get(this, 'socket').joinSchedule(cb);
},
resetController() {
get(this, 'socket').leaveSchedule();
},
});
Call callback method in socket service,
import Ember from 'ember';
import PhoenixSocket from 'phoenix/services/phoenix-socket';
const {
get,
inject,
} = Ember;
export default PhoenixSocket.extend({
session: inject.service(),
store: inject.service(),
joinSchedule(cb) {
const channel = this.joinChannel(`v1:my-model`);
channel.on('sync', (payload) => cb(this._handleSync(payload)));
},
_handleSync(payload) {
return get(this, 'store').pushPayload(payload);
},
});
Upvotes: 0
Reputation: 191
You can trigger an event from your websocket service when you receive a message on the socket, then subscribe to it in your route and then call refresh() to reload your model. There is also https://github.com/ember-data/ember-data-filter - which retuns live array.
Upvotes: 1