Search code examples
ember.jswebsocketember-data

Hiding deleted records from templates


I am trying to figure out if there's a better way to hide deleted records from a template and remove them from computed properties. My ember app is listening to a web socket and if another user deletes a record, there's a handler that removes that record from the store:

_handleDelete({id, type: modelName}) {
  const record = get(this, 'store').peekRecord(modelName, id);

  if (record) {
    get(this, 'store').deleteRecord(record);
  }
}

What I'm currently doing is defining a computed property that filters out any records where isDeleted is true, and using that for other collections:

allItems: computed.filter('items.@each.isDeleted', function(item) {
  return !get(item, 'isDeleted');
}),

Then I'm using this computed property to build other computed properties:

materialCosts: computed.filterBy('allItems', 'proposalCostType', 'material'),

This works fine, but the issue I'm having is that I didn't start with this, so many of my templates are referencing the hasMany relationship directly (items), rather than the filtered collection (allItems). It's a huge app so I'd have to update a TON of templates to get this working, and I don't think that's time efficient.

I never want a deleted record to ever display in the template and don't want any computed properties to be able to reference them, so ideally I'd be able to do something where the items relationship automatically filters out deleted records and is updated when they are removed.

I'm on Ember Data 2.14 so maybe there's a newer version that can do this? Update - Also tried on 2.18 just to see if it was the version I was using.

Edit

I tried what @Gennady Dogaev suggested below with unloading the record rather than deleting it, but ember gives me errors with computed properties that are watching the collection. For example, this one:

approvableItems: computed('items.[]', function() {
  return get(this, 'items').filterBy('isApprovable');
}),

... gives me an error that isApprovable can't be called on an undefined object.


Solution

  • If other user deleted record, it would be more correct to unload record from store. After that templates will stop rendering it, as if it never was in the store.

    I don't recommend using deleteRecord in this case, because it dirties data in ember store. You supposed to call record.save() after calling deleteRecord but it will make http request to API which I think you don't want in described case.


    In some cases you may encounter cryptic js errors:

    1. If you try to push into store a new record with the same id as deleted
    2. If user tries to re-visit route which tries to load deleted/unloaded record. This can achieved by using back/forward buttons in browser

    If you encounter some errors in these scenarios, try to use following method for unloading records:

    /**
     * This function unloads record from store and clears some private internals to avoid errors when UI tries to read
     * unloaded record from store
     *
     * @see https://discuss.emberjs.com/t/reusing-ids-for-soft-deleted-records/13715/6
     * @param store
     * @param model
     * @param id
     */
    export default function(store, model, id) {
      let record, recordId, modelName;
      if (typeof model === 'object' && typeof model.get === 'function') {
        record = model;
        recordId = model.get('id') || id;
        modelName = model.constructor.modelName;
      } else {
        recordId = id;
        modelName = model;
        record = store.peekRecord(modelName, recordId);
      }
      if (record && !record.isDeleted) {
        store.unloadRecord(record);
      }
      if (recordId) {
        delete store.get('_identityMap')._map[modelName]._idToModel[recordId];
      }
    }
    

    I don't know if it still needed in latest version but I needed this code not so far ago.