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!