Dealing with calling `_super()` with template pattern for less coupled code?


#1

Today I finished Chapter 6 Acquiring behaviour through inheritance from Practical Object-Oriented Design in Ruby by Sandi Metz. I’m wondering how I could use the template pattern in init() hook.

The original solution is:

// app/components/accounts/members/add-new-member/component.js
init() {
  this._super();
  this.set('memberObject', Ember.Object.create());
  this.set('memberObject.role', this.get('defaultRole'));
},

For now the component is a concrete class (no inheritance involved). I can move the behaviour from init() like:

// app/components/accounts/members/add-new-member/component.js
init() {
  this._super();
  this.postInit();
},

postInit() {
  this.set('memberObject', Ember.Object.create());
  this.set('memberObject.role', this.get('defaultRole'));
}

I could go one step further and extend a Component or Object class with postInit:

// app/components/shared/component.js
init() {
  this._super();
  this.postInit();
},

postInit() {
  // implemented in sub-classes
}
// app/components/shared/component.js
postInit() {
  this.set('memberObject', Ember.Object.create());
  this.set('memberObject.role', this.get('defaultRole'));
}

Would it make it more bullet-proof or make it art for the art’s sake?


#2

My personal opinion is that’s just “art for the art’s sake”. A few reasons:

  1. You’re introducing new “APIs” for your local application that are not native. This in itself isn’t bad, but it does add to the cognitive load for other developers, so you should make sure there is a value add.
  2. You’re not really going to avoid having to call this._super() because if you had a second component that inherited from “app/components/shared/component.js” then inside of that postInit you’d have to call this._super().
  3. There are actually other lifecycle hooks for a component that may be better depending on your use case.

Also, a few other notes.

  • When calling this._super() you should always use the spread operator like so: this._super(...arguments);. That will ensure all args are pass to the base class. Even in cases where the function doesn’t have defined arguments (like init), it’s a good future proofing practice.
  • There is an Ember.on event for init. We use it a lot in our app, but disclaimer there have been some rumblings about it and some talk of deprecating it. You can use this several times. You would use that like so:
export default Ember.Component.extend({
  _setupMember: Ember.on('init', function() {
    this.set('memberObject', Ember.Object.create());
    this.set('memberObject.role', this.get('defaultRole'));
  }),

  /* ... */

  _setupOtherThing: Ember.on('init', function() {
    this.set('otherThing', Ember.Object.create());
    this.set('otherThing.foo', 'bar');
  })
});

#3

If each component extended MyComponent or whatever the name could be, postInit would always be called. But if there was a component that extended FooComponent I’d have to call _super() anyway.

This could be solved either by moving some behaviour to a mixin, or, moving the postInit to re-opened Object class, (or just Component one).

You’re right, whatever solution I’d take, this would add another level of complexity for other developers.

// app/components/shared/my-component.js

export default Ember.Component.extend({
  init() {
    this._super();
    this.postInit();
  },

  postInit() {
    // implemented in sub-classes
  }
});
// app/components/foo/component.js

export default Ember.MyComponent.extend({
  postInit() {
    console.log('postInit from fooComponent');
  }
});
// app/components/bar/component.js

export default Ember.MyComponent.extend({
  // I don’t have to implement `postInit`
});

In what order are init hooks called?

Thanks for the tip!


#4

It’s undefined. You can’t really depend on it either way. The execution order for events can, and have, changed.

You’re welcome!