There are three major changes to templates in Octane compared to classic Ember:
- Angle Bracket Syntax
- Named Arguments
- Required
this
Angle Bracket Syntax
When you are using a component in a template, you can invoke it using Angle Brackets (<...>
) instead of curly braces ({{...}}
).
The component itself will work the same as it did before.
<TodoList as |item|>
<Todo @item= />
</TodoList>
Benefits of Angle Brackets
Angle Brackets have a number of benefits:
Single word component names are completely OK in angle bracket form.
It is clear to your collaborators you are using a component and not a helper in a template.
Standard attribute values applied to the component are treated like plain-old HTML attributes. This means you can assign any valid HTML attribute, and it will be reflected onto the component directly:
<Todo
role="list-item"
data-test-todo-item
data-test-id=
class="todo "
/>
As you can see, both literal and bound values can be set on attributes, and
attributes can be used without setting a value at all, just like HTML
attributes. The component you are invoking decides where to put these attributes
by using the special ...attributes
syntax. This will be discussed later in
the section on components.
For classic components, only attributes that were explicitly listed by the component you are invoking would be placed on the component's wrapper element.
- Arguments and attributes are distinguished from each other when using a component. With curly brace style components, every value you pass to the component is an argument - a JS value that is passed to the component class so it can be used in the component's template:
With angle brackets, since you can pass standard HTML attributes to the
component directly, we need a way to distinguish between those and the
component's arguments. To do this, we use the @
symbol:
<CustomInput @value= />
This allows you to see at a glance whether a value is an argument, which will likely affect the JS of a component, or an attribute, which will likely affect the HTML of a component.
Getting used to Angle Brackets
Here are the main differences between angle bracket and curly syntax:
The component name is in
CapitalCase
instead ofkebab-case
.{{my-component}}
becomes<MyComponent />
.Components open and close in the same way as HTML elements. Components that do not accept a block can use the self closing syntax (a trailing slash) just like
<img />
or other tags.Arguments are passed by adding
@
to the front of the argument name:
<TodoItem @item= />
- When you pass a bound value to a component, remember that it needs to be wrapped in curly braces:
<Todo @done= />
Like HTML, all values for attributes that are not wrapped in strings are coerced to strings. If you want to pass a boolean or number to a component and not have it coerced to a string, wrap it in curly braces:
<Todo @done= maxItems= />
- Yielded values work the same as in curly invocation:
<TodoList as |item|>
<Todo @item= />
</TodoList>
- Yielded components can also be invoked with angle bracket syntax:
<TodoList as |Item|>
<Item />
</TodoList>
Positional arguments (e.g.
{{my-component this.someValue}}
) are not available in angle bracket invocation, since there is some ambiguity between their behavior and the behavior of standard HTML attributes (HTML attributes without=
default to truthy). If you still need positional arguments, you must use the component with curly bracket syntax.If you are updating a classic component to use angle bracket syntax, you can also overwrite the parameter array with a named argument instead. For instance, if
my-greeting
had the following implementation:
import Component from '@ember/component';
export default Component.extend({
}).reopenClass({
positionalParams: 'params'
});
To invoke it using angle bracket syntax, you would do the following:
<MyGreeting @params=>
You can use either angle bracket or curly brackets invocation for a given component within the same app, and even within the same template. This allows for gradual migration.
Angle bracket syntax works for invoking components of any type, whether they are classic components, Glimmer components, or any other type of component.
Curly syntax is still appropriate for some types of components! Check out
ember-control-flow-component
for an alternative to Glimmer components and angle bracket syntax for such components.
Named Arguments
With angle brackets, there is a new syntax for passing arguments to a component:
Within the component, you can now access these arguments directly with the same syntax:
Collectively, this is referred to as named arguments.
Benefits of Named Arguments
Named Arguments have a number of benefits:
When you see a named argument used in a component's template, you can tell immediately that it is a value that was passed to the component, without looking at the component's class.
Named arguments always refer to the original value that was passed to the component, so you can also be sure that the value was never mutated by the component's class.
Teams can gradually refactor an app to use named arguments, separately from upgrading to angle bracket invocation. You don't need to worry about whether the parent used angle brackets or curly brackets. For example, this works just fine:
Getting used to Named Arguments
The most important thing to know about named argument syntax is that an argument
with an @
always refers to the original value that was passed when the
component was invoked. If you change that value in a classic component, it will
not update:
import Component from '@ember/component';
export default Component.extend({
init() {
this.set('title', this.title.toUpperCase());
},
});
If you need to provide a default value, you'll have to do it via a getter:
import Component from '@glimmer/component';
export default class BlogPostComponent extends Component {
get title() {
return this.args.title || 'Untitled';
}
}
Note: The above sample uses Glimmer components - we'll be covering these in detail later on.
Or by using a helper in the template:
If you find yourself forgetting to add the @
symbol before named arguments, it
may be helpful to think of how the child template mirrors argument being passed
into a component via angle bracket invocation.
Required this
in templates
Finally, one thing you may have noticed in the above examples is a lot more
references to this
in the template. Values that are rendered from the local
component or controller instance that backs the template must now have this
prepended at the beginning of the path:
Benefits of this
in templates
The reason for this change is to provide extra clarity to both users reading
templates, and the compiler. Without explicitly referring to this
, a lot of
handlebars statements are pretty ambiguous - for instance, {{title}}
could be
a helper, a local variable, or a component property.
Getting used to this
in templates
You can think of this
as meaning, an argument came from this
component or
controller, not a parent context.
Local variables, introduced via a yield, can still be referred to directly
(without this
) since they're unambiguous:
If you forget to use this
when you are supposed to, it will fall back to the
context of the component or controller context that backs the template. However,
the fallback behavior is deprecated and will be removed in future major versions
of Ember (4+).