Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling network connectivity and promise rejections in Ember

Tags:

ember.js

I am trying to implement an application-level error handler to catch failed promises within my application. Most of the suggestions I've seen around this have to do with error logging, but I actually want to trigger a modal window to let my users know they're no longer connected to the internet + can only read data.

I'd like to trigger an action from within the RSVP.onerror handler. The only way I've managed to do this so far is with something like this:

Ember.RSVP.configure('onerror', function(e) {
  window.App.__container__
    .lookup('controller:application')
    .send('showModal', 'disconnected');
});

but it seems a bit hacky to me.

Is there somewhere more appropriate to write this code? More generally, are there any best practices around notifying users of a lost internet connection?

like image 615
Sam Selikoff Avatar asked Oct 20 '22 04:10

Sam Selikoff


2 Answers

Ember has a built in error route (docs here).

Depending on how you want to handle other kinds of errors in your app, you could add views/error.js and run a method on willInsertElement in that view that tests whether the user is offline and, if true, sends the showModal action.

Alternatively, failed promises can automatically be caught in the original promise and trigger some method that has been injected into the controller or whatever instance is making the promise:

Controller:

this.set('thing').then(function() {
  // Success
}, this.offlineError()); // Failure

Initializer (note the syntax is for ember-cli):

export default {
  name: 'offline-error',

  initialize: function(container, app) {
    var offlineError = function() {
      // or something like this
      container.lookup('controller:application').send('showModal', 'disconnected');
    };

    app.register('offlineError', offlineError, { instantiate: false });

    // Inject into routes, controllers, and views.
    Em.A(['route', 'controller', 'view']).forEach(function(place) {
      app.inject(place, 'offlineError', 'offlineError:main');
    });
  }
};

The above code makes the offlineError method available in all routes, controllers, and views but the beauty of it is that the method has access to the container without using the hacky private property.

like image 173
Duncan Walker Avatar answered Oct 23 '22 03:10

Duncan Walker


I ended up wrapping Offline.js in a service. I'm using ember-cli.

// services/connection.js

/* global Offline */
import Ember from 'ember';

// Configure Offline.js. In this case, we don't want to retry XHRs.
Offline.requests = false;

export default Ember.Object.extend({

  setup: function() {
    if (Offline.state === 'up') {
      this._handleOnline();
    } else {
      this._handleOffline();
    }

    Offline.on('down', this._handleOffline, this);
    Offline.on('up', this._handleOnline, this);
  }.on('init'),

  _handleOffline: function() {
    this.set('isOffline', true);  
    this.set('isOnline', false);  
  },

  _handleOnline: function() {
    this.set('isOnline', true);  
    this.set('isOffline', false);  
  }

});

I injected the service into controllers and routes:

// initializers/connection.js

export default {
  name: 'connection',
  initialize: function(container, app) {
    app.inject('controller', 'connection', 'service:connection');
    app.inject('route', 'connection', 'service:connection');
  }
};

Now in my templates, I can reference the service:

{{#if connection.isOffline}}
  <span class="offline-status">
    <span class="offline-status__indicator"></span>
    Offline
  </span>
{{/if}}

(Offline.js also has some packaged themes, but I went with something custom here).

Also, in my promise rejection handlers I can check if the app is offline, or if it was another unknown error with the back-end, and respond appropriately.


If anyone has any suggestions on this solution, chime in!

like image 32
Sam Selikoff Avatar answered Oct 23 '22 02:10

Sam Selikoff