Converting an Ember Component to a Glimmer Component, Part 1
Learn the basics needed to convert an Ember native class component to a Glimmer component.
Summary
In this video we convert an Ember component using native class syntax into a Glimmer component. This will involve:
- How to actually use Glimmer to make your component
- Removing the wrapper tag
- Replacing the
didInsertElement
lifecycle hook - Updating the component argument syntax:
- using
@
in template - using
this.args
in JavaScript files - no implicit
this
- using
How to use Glimmer to make your component
To use Glimmer to render components, import Component
from @glimmer/component
rather than @ember/component
:
import Component from '@glimmer/component'
Removing the wrapper tag
Glimmer components do not have wrapper elements, so we no longer need to define any tagNames
in our JavaScript file. Instead, we can put any HTML tag we want directly in our template.
Next we need to update the didInsertElement
hook. This lifecycle hook is not supported in Glimmer components. To replace logic that happens in didInsertElement
, we can use the did-insert
modifier from the ember-render-modifiers
addon.
Updating the component argument syntax
An important thing to understand about Glimmer components is that passed-in arguments are referred to differently than internal properties.
Properties that are passed into your component are now immutable. You can't change them and you can't overwrite them. For this reason, the syntax you use to refer to passed-in arguments in your components is different in both your JavaScript and template files.
In a JavaScript file, you use the args
object to reference a passed-in argument. In a template file, you use the @
sign (instead of this
) to reference a passed-in argument. For more information about this, check out the video or see the official migration cheat sheet.
If you do not prefix your external props with this syntax, your component will not read the correct values and it will break.
No implicit this
Finally, updating components to use Glimmer changes the relationship the component has with this
. In a Glimmer world, this
is reserved for properties defined directly on the component object. This idea is called no-implicit-this
and can be enforced with a linter rule of the same name with ember-template-lint
.
While not technically required in current versions of Ember, it is considered good practice to follow and implicit-this
will be deprecated in Ember 4. If you need to convert your whole app away from implicit-this
there is a codemod that will do most of the work for you.
Here's the diff of all these changes applied to our files:
// app/components/comments.js
import {tagName} from '@ember-decorators/component';
import {action, computed} from '@ember/object';
import {inject as service} from '@ember/service';
import {gt, readOnly} from '@ember/object/computed';
+ import Component from '@glimmer/component';
- import Component from '@ember/component';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
- @tagName('section')
export default class Comments extends Component {
@service
store;
@tracked isShowMoreExpanded = false;
@tracked comments = [];
@tracked allowNewComments = false;
@readOnly('comments.length')
commentCount;
@gt('commentCount', 5)
isTooManyComments;
@computed('isTooManyComments', 'isShowMoreExpanded')
get shouldShowExpandCommentOption() {
if (this.isTooManyComments && !this.isShowMoreExpanded) return true;
return false;
}
@computed('isTooManyComments', 'isShowMoreExpanded')
get slicedComments() {
if (this.isTooManyComments && !this.isShowMoreExpanded) {
return this.comments.slice(0, 5);
} else {
return this.comments;
}
}
@action
expandComments() {
this.isShowMoreExpanded = true;
}
- didInsertElement() {
- this.fetchComments.perform(this.post);
- }
@task(function* (post) {
const comments = yield this.store.query('comment', {
post_id: post.id,
include: 'author',
});
this.comments = comments;
})
fetchComments;
}
// app/components/comments.hbs
- {{#if fetchComments.isRunning}}
- <span data-test-comments-loading>
- <div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
- </span>
- {{else}}
- <div data-test-post-comments>
- {{#if allowNewComments}}
+ <section {{did-insert (perform this.fetchComments @post)}}>
+ {{#if this.fetchComments.isRunning}}
+ <span data-test-comments-loading>
+ <div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
+ </span>
+ {{else}}
{{#if this.allowNewComments}}
<NewComment @currentUser={{@currentUser}} />
{{/if}}
- {{#each slicedComments as |comment|}}
+ {{#each this.slicedComments as |comment|}}
<Comment @comment={{comment}}/>
{{/each}}
- {{#if shouldShowExpandCommentOption}}
+ {{#if this.shouldShowExpandCommentOption}}
<button
class="expand-comments-button border-gray-500 text-gray-500 hover:border-teal-500 hover:text-teal-500"
{{action "expandComments"}}
>
Show more comments
</button>
{{/if}}
- </div>
- {{/if}}
+ {{/if}}
+ </section>