Search code examples
ember.jsember-data

Ember centralised error handling within an Action


Inside an Ember controller I have a number of actions which perform data events (create update and delete) on the store.

Each of these requests has the same error handling logic which is repeated many times. If there a way of centralising this error logic?

  1. Given this logic will be used by many routes, what would be the best location in Ember for it to live?
  2. How can the context be preserved so this is still available to send toast type of messages to the DOM ?

Here is an example of what I have now

createEntryRecord() {
  // This action is a 'create' button on a modal
  set(this, 'isLoadingModal', true);  // shows loading spinner
  let dbid = this.get('dbidModal');
  let type = this.get('entryModalType');

  this.get('store').createRecord('my-store', {
    type,
    dbid
  })
  .save()
  .then((record) => {
    get(this, 'flashMessages').success(`Created`);  // Toast message
  })
  .catch((e) => {
    // Begin of the error handling logic
    let errors = get(e, 'errors');
    if (errors.length) {
      errors.forEach(err => {
        let field = err.source.pointer.split("/").pop();
        get(this, 'flashMessages').danger(`${field}: ${err.detail}`);
      });
    } else {
      let message = get(e, 'message');
      get(this, 'flashMessages').danger(`${message}`);
    }
  })
  .finally(() => {
    set(this, 'isLoadingModal', false);  // stop the loading spinner
    set(this, 'isEntryModalShown', false);
  });
},

Solution

  • I would suggest a solution that utilises Ember's Object Model Mixins.
    First you create a common Mixin for all your models:

    // app/mixins/flashing-model.js
    import Mixin from '@ember/object/mixin';
    import { inject as service } from '@ember/service';
    
    export default Mixin.create({
        flashMessages: service(), // can be accessed via this.get('flashMessages')
        save(...args) {
            const superSavePromise = this._super(...args);
            superSavePromise.then(() => {
                // your positive logic
            }).catch(e => {
                // your negative logic
            });
            return superSavePromise;
        }
    });
    

    Then you include it in each of the models. For example:

    // app/models/some-model.js
    import FlashingModel from '{your-app-name}/mixins/flashing-model';
    import DS from 'ember-data'
    export default DS.Model.extend(FlashingModel, {
        // ...
    });
    

    After that you can edit your controller:

    this.get('store').createRecord('my-store', {
        type,
        dbid
    })
    .save()
    .finally(() => {
        // ...
    });
    

    So regarding your questions:
    1. Ember Mixins (under app/mixins directory) can be a good place to host common logic.
    2. When implementing the above solution you don't really need to preserve the controller's this context, since the flashMessages service is injected to each model. Hence, you can access the service within the save method's context, which is the this of model.