Converting an Ember Component to a Glimmer Component, Part 2

Let's update our new Glimmer component with a few more idioms from Octane.

Summary

In this video we'll complete the Glimmer component update process!

We're now technically using Glimmer for our components, but there's a few more ways to bring our components into Octane and Glimmer best practices.

This video covers:

  • Template colocation
  • Action syntax and the on modifier
  • Computed/macros to getters
  • How to set default values

Template colocation

To make our component match Octane conventions, let's move our component template files into the components directory. This is called template colocation, and has been available since Ember 3.13. With this update your component JavaScript files and template files can live in the same directory.

You can move your files manually, or if you've got lots of files to move, you can use the codemod: ember-component-template-colocation-migrator.

Your components should not need any changes, and Ember will be moving in this direction going forward. You can read more about the reasoning behind this update in RFC 0481 - Template colocation

Actions

Recall that the actions hash has been replaced by the @action decorator. This means that your actions no longer need to be name-spaced to an actions object, and both your JavaScript file and your template file can call these decorated actions as normal JavaScript functions. This also means we don't need to refer to action names as strings (e.g. {{action 'functionName'}}) anymore.

This means we can replace our expandComments action with {{action this.expandComments}}.

We can take it one step further and use the on modifier. This is the latest way to add interactions to elements in Ember. We can use {{on "click" this.expandComments}} to tell the template to run expandComments on click. Check out Sam's previous video for a lot more detail about the on modifier.

Computed and computed macros

To make our app even more modern, we can refactor away from @computed and related macros. They still work for now, but you should aim to convert your computed properties and macros to plain getters as computed properties will become deprecated in future version of Ember.

Here are two examples:

// old
@gt('commentCount', 5)
isTooManyComments;

// new
get isTooManyComments () {
  return this.commentCount > 5;
}
// old
@computed('isTooManyComments', 'isShowMoreExpanded')
shouldShowExpandCommentOption() {
  return this.isTooManyComments && !this.isShowMoreExpanded
}

// new
get shouldShowExpandCommentOption() {
  return this.isTooManyComments && !this.isShowMoreExpanded
}

If you're unfamiliar with @tracked, you may be wondering how Ember will know to recalculate these properties when one of their dependent properties changes, since we've dropped the dependent key string we're used to using inside of computed().

Well, since we've defined the properties that we want to track using the @tracked decorator, Ember can automatically determine when it needs to recalculate getters. @tracked is the key feature of Glimmer that enables this!

We no longer have to declare dependencies manually for every computed property. If a getter accesses a tracked property, or accesses another getter that itself accesses a tracked property, it will automatically update for you. Also, since passed-in arguments are tracked, any getter that accesses an argument will also update automatically when that argument changes.

You can read more about the differences between computed properties and getters here.

Default values

Finally, we'll cover how to set default values in a Glimmer component.

You might have noticed that we introduced a bug onto our page – before the conversion, we had a textarea where a user could enter a new comment. This element's visibility is controlled by the allowNewComments prop, which is defined by default as false in our component. However, the caller is passing in true as an argument for the prop, attempting to overwrite the default.

This setup worked in the classic version of components, because default property values could be overridden by arguments of the same name. In Glimmer components, however, all arguments are preserved in the name-spaced args object, and they no longer automatically overwrite properties of the same name on the component instance.

We can fix this by writing a new getter. Looking at our template, we can see it's currently accessing the this.allowNewComments property of our component, and in our JavaScript file, that property is set to false.

Let's update this property to be a getter:

get allowNewComments() {
  return this.args.allowNewComments ?? false;
}

The getter first checks whether an allowNewComments arg was passed in or not from the caller. If it was, it uses it, and otherwise, it falls back to a default value of false.

Changing this.allowNewComments from a property to a getter doesn't require any changes to our template – this.allowNewComments still works just the same. This is an intentional part of the design of Glimmer components. And now our page works just like before!

Questions?

Send us a tweet:

Or ask us in Inside EmberMap, our private Slack workspace for subscribers.