What's New with Components as of Ember 3.4

September 25, 2018

Named Arguments (as of 3.1)

In short, with Named Arguments, {{title}} becomes {{@title}} in the template of {{my-header title=title}}. This helps distinguish component arguments from component properties such as local component state and computed properties.

Here is the original blog post on Named Arguments.

Angle Bracket Invocation (as of 3.4)

Prior to Ember 3.4, components were invoked using double curlies. Now we can invoke them using angle bracket invocation.

Non-Block Component


{{characters-remaining max=36 value=name}}

becomes


<CharactersRemaining @max={{36}} @value={{name}} />

Block Component


{{#characters-remaining max=36 value=name as |charactersRemaining|}}
  {{charactersRemaining}} characters remaining
{{/characters-remaining}}

becomes


<CharactersRemaining @max={{36}} @value={{name}} as |charactersRemaining|>
  {{charactersRemaining}} characters remaining
</CharactersRemaining>

Because component arguments are prefixed with @, any other attributes on the component invocation will become HTML attributes. For example:


<CharactersRemaining
  @max={{36}}
  @value={{name}}
  data-test="characters-remaining" />

The data-test attribute will automatically be set as an HTML data attribute on the component’s element. No need for attributeBindings here!

We can even use single word component names with angle bracket invocation! However, this doesn’t work when generating a component via Ember CLI at the moment. We can get around this by generating a component with a hyphen and then renaming it to a single word.

If your app isn’t on Ember 3.4 or above, you can install the ember-angle-bracket-invocation-polyfill, which allows you to use angle bracket invocation all the way back to Ember 2.12.

Here is the RFC on Angle Bracket Invocation.

Template-only Glimmer Components (as of 3.1)

Template-only glimmer components are an optional feature that allow us to have components without a JavaScript file. This is great for those cases where we had components with an empty class definition. To get started, install the optional features addon:

ember install @ember/optional-features

Next, enable it with the following in config/option-features.json:

{
  "template-only-glimmer-components": true
}

Now, create a template and that is your component! One thing to note is that your curly expressions need to be prefixed with @. If we have a component called my-header with a title attribute, the template would go from this:


<header>
  {{title}}
</header>

to this:


<header>
  {{@title}}
</header>

HTML Attribute Spreading with ...attributes

One of my favorite features of angle bracket invocation is being able to capture all HTML attributes and spread them over another element via ...attributes. I have found this particularly useful with template-only Glimmer components. For example, let’s say we created a template-only Glimmer component called required-action-callout with a template like this:


<div class="alert alert-warning">
  ...
</div>

We can invoke it as such:


<RequiredActionCallout class="mt-2" data-test="required-action-callout">
  ...
</RequiredActionCallout>

In order for div.alert.alert-warning to get the class mt-2 and the data-test attribute, we can modify our template as such:


<div class="alert alert-warning" ...attributes>
  ...
</div>

The final result will be:


<div class="alert alert-warning mt-2" data-test="required-action-callout">
  ...
</div>

If you put ...attributes first, such as:


<div ...attributes class="alert alert-warning">
  ...
</div>

any attributes in ...attributes that are present on the element will win out resulting in:


<div class="mt-2" data-test="required-action-callout">
  ...
</div>

Personally, I have found myself only using ...attributes as the last thing on an element. Another thing to note is that ...attributes can only be used within an element position. {{log attributes}} logs undefined.

Here is the original blog post on Template-only Glimmer Components.

Disclaimer: Any viewpoints and opinions expressed in this article are those of David Tang and do not reflect those of my employer or any of my colleagues.