Best Practice: What helpers might do


#1

Hi everybody,

Ember api reference states:

Ember Helpers are functions that can compute values, and are used in templates. https://emberjs.com/api/ember/release/classes/Helper

Thinking about an issue that look like not to have a direct solution in Ember like the long discussed “long polling”, I had the idea to implement an helper to be placed on the application template that will satisfy my needs asynchronously by means of javascript promises.

That will lead to a solution that is not bare javascript hidden in an external module in vendor folder, but it might consist in a misuse of the Helper class.

What do you people think about it?


#2

Can you describe your use case a little more? We use short-polling pretty heavily in one of our apps (switching to websockets soon). I believe a service is probably what you’re looking for for short or long polling solutions.

A helper should generally be a simple function that returns a value that is meant to be used in a template. For example ember truth helpers contains a bunch of helpers like “and” which just returns a boolean and of all passed values, or “not” which just returns the boolean inverse of the passed value. A more complicated helper example that we use is for permissions, aka you pass in two arguments like ‘read’ and ‘user’ and the helper returns a boolean telling you whether or not you have that permission so you can render a button or edit controls or something. This helper uses a service to determine the permissions:

{{#if (permission-to 'write' 'user')}}
...
{{/if}}

A service is useful for any piece of global data or state or functionality that you may need to use throughout your app, or control throughout your app, or just from the application route/controller. Here is a simplified short-poller example from our app (it uses raw ajax instead of ember data but pretty much the same thing, and you could shove the records into the store either way):

import $ from 'jquery';
import { bind, cancel, later } from '@ember/runloop';
import Service, { inject as service } from '@ember/service';
import ENV from '../config/environment';

export default Service.extend({
  session: service(),

  currentStats: null,

  pollFrequency: 3000,

  start: function(/*subscriber, */){
    if(this.get("pollTimer") !== undefined){
      cancel(this.get("pollTimer"));
    }
    this.statsPoll();
  },
  stop: function(/*subscriber*/){
    cancel(this.get("pollTimer"));      
  },
  statsPoll: function(){
    let self = this;
    $.ajax({
      url: `${ENV.APP.API_HOST}/stats`,
      type: "GET",
      contentType: "application/json",
      beforeSend: function (xhr) {
        var token = self.get('session.session.authenticated.access_token');
        if(token){
          xhr.setRequestHeader ("Authorization", "Bearer " + token);
        }
      }
    }).then(bind(this, response => {
      this.set('currentStats', response);
      if(ENV.environment !== "test") {
        this.set("pollTimer", later(this, this.statsPoll.bind(this), this.get("pollFrequency")));
      }
    })).catch((/*error*/) => {
      // make sure to keep the poller going even if there was an error
      if(ENV.environment !== "test") {
        this.set("pollTimer", later(this, this.statsPoll.bind(this), this.get("pollFrequency")));
      }
    });
  }
});

EDIT: basic rules of thumb:

  1. If you need something that returns a single value to be used in a template, use a helper
  2. If you need something more complicated that either has some javascript bits and/or more than just a simple DOM element/value, use a component
  3. If you have global state or some kind of processing task that needs to happen in the global/application context (like polling) use a service

#3

Thank you @dknutsen, that was exactly the type of answer I needed.

The use case I have on the desk right now is to implement a simple notification engine. I have to say, this functionality is already existing in this app, but it is implemented as a bare javascript object in an external module due to the revamping of an existing old-styled ajax app which includes porting it step-by-step to ember.

I am relatively new to ember and I did not know about services (it is not mentioned in the guides and on the api doc page there is no general description) so I never consider it as an option. I don’t like to query data in components, and routes are the best place to handle route-related data transitions. I was looking for a way to “legally” make asynchronous, route indipendent data transitions and I think you just pointed the way to me. Ignoring all about services, I was thinking about an helper which renders nothing but just push data in the store, but it didn’t sound good.


#4

@beddu yeah service definitely sounds like what you want. There are some docs here under “Application Concerns” which may be helpful as well, but the example I posted would probably be a pretty good starting point.


#5

Ops…I didn’t see that. Thanks again!