Peter Brown
Peter Brown

Reputation: 51707

Adding new data through websocket when using store.query in route

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

Answers (3)

Ember Freak
Ember Freak

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,

  • You can subscribe to 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
        }
    
  • To avoid memory leak need to 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

maheshiv
maheshiv

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

xoma
xoma

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

Related Questions