Proper use of Ember on()

This is an aggregation of content presented on the same topic in other blog posts as well as in conversation that has occurred in the Ember Community Slack and has been reviewed by Robert Jackson (@rwjblue) and Chris Thoburn (@runspired) for accuracy. I point this out because while I should not be considered the authoritative voice on this subject the material has been researched, vetted and reviewed.

In the lifecycle of Ember.js components there are several events that are/can be triggered, which are:

  • init
  • willInsertElement
  • didInsertElement
  • willClearRender
  • willDestroyElement
  • didDestroyElement
  • didUpdateAttrs
  • didUpdate
  • didReceiveAttrs
  • willRender
  • didRender

The init event is also triggered for objects.

It is possible to define functions to be executed when these events are triggered and there are two different patterns for doing so, though one is preferred over the other.

Discouraged pattern

The use of on(), which is one of the patterns, is STRONGLY discouraged. This primarily has to do with the execution order of the functions attached to these events. on() is scheduled in the events queue, so if you define more than one on() against an event you will not be able to determine the execution order of the on()s in relation to one another. This indeterminate execution order also presents itself when mixins are employed or inheritance is changed.

Something to also keep in mind with this pattern is that on( 'init' ) is actually also different in behavior than init(). The former actually occurs after initialization so any property initialization you do triggers change notifications whereas any property initialization you do during init() does not need to cause those changes. This means that work done after a property is set following the event may have had to have been done twice.

Preferred pattern

The preferred pattern to use is to define the function as a callback assigned to the event name:

init() {
    this._super(...arguments);
    ...
},

didInsertElement() {
    ...
    this._super(...arguments);
}

When using this pattern you MUST ALWAYS call this._super(). While a technical argument can be made that not making this call is allowable in some circumstances, not calling it will increase your technical debt. When future refactoring occurs, such as adding a mixin, you will be forced to change previously written, and seemingly unrelated, code to now call this._super().

In init() the call to this._super(...arguments) should be before using this for anything else.

Not every callback defined for an event receives arguments but for those that do, such as init, remember to also pass along ...arguments, such as this._super(...arguments). It's really not a bad idea to just get in the habit of using this syntax for every callback irrespective of whether there are arguments to pass along or not.

The very rare exception

The only time to consider using on() is if the execution order of your functions definitely does not matter AND you're in a situation in which you need the behavior to not break if _super() is not called by extending code, such as in a mixin provided by an addon.

Show Comments