Angle Bracket Invocation for Nested Components (3.10)
Since 3.4 we've had Angle Bracket components, but this feature fills in a missing piece: nested components.
Summary
- Ember 3.10 Release
- Nested Invocations in Angle Bracket Syntax RFC
- Angle Bracket Invocation Polyfill (for apps below 3.10)
- Ember Angle Brackets Codemod
Ember 3.4 introduced Angle Bracket components, but we never had a way to use angle brackets with components in nested directories. Ember 3.10 added new syntax for using angle-bracket syntax with nested components. Let's check it out!
First, let's quickly review some differences between the curly-bracket and angle-bracket syntaxes:
{{some-component name="Tom" class="my-class"}}
<SomeComponent @name="Yehuda" class="my-class" />
In curlies, we use bare words to pass both JavaScript data (like the name
property above) as well as HTML attribute (like class
) into components. This made our templates harder to read when trying to understand JavaScript data flow, and it also necessitated some clunky APIs like attributeBindings
.
With angle bracket syntax, we use the @
at sign to distinguish JavaScript data from HTML attributes. No more attribute bindings, and improved visual clarity.
Now let's talk about nested components. We've always been able to invoke nested components with curlies:
{{forms/input}}
but before 3.10, we had no equivalent with angle brackets. In 3.10, now we do:
<Forms::Input />
Looks great!
Here's what block-form looks like:
<Forms::Label>
I am a label
<Forms::Input />
<Forms::Label>
Here's yielded data:
<Forms::Label as |formId|>
I am a label for form {{formId}}
<Forms::Input />
<Forms::Label>
Now let's talk about contextual components. With curly invocation, we're used to writing contextual components like this:
{{#forms/label as |label|}}
I am a label
{{label.input}}
{{/forms/label}}
Here, {{label.input}}
is a contextual component, a component invoked using a path expression. Notice the input
is lower-case. That's been the convention in Ember thus far.
Let's see the same contextual component used with angle-bracket syntax:
<Forms::Label as |label|>
I am a label
<label.input />
<Forms::Label>
This works, and if the contextual component is implemented with a lower-case input
as its yielded contextual property, this is how you'll need to invoke it.
However, if you are authoring components yourself and have control over what properties are yielded, you should start using capitalized properties to reference components. Here's an example:
<Forms::Label as |label|>
I am a label
<label.Input />
<Forms::Label>
Now our contextual component is Input
, and it looks much clearer in our template, as all other components will be invoked with CapitalCase.
We should still use camelCase for yielded JavaScript data:
<Forms::Label as |label|>
I am a label for {{label.formId}}
<label.Input />
<Forms::Label>
So, the naming is up to you, but label.formId
and label.Input
are a clear signal as to what these properties represent.
There's another good reason to use capitalized letters. Suppose our Label
only yielded out a component as its single positional parameter:
<Forms::Label as |input|>
I am a label
<input />
<Forms::Label>
This works... but <input />
makes it very unclear that we're invoking our own Ember component here. That's because <input />
looks a lot like the HTML <input />
! And in fact, there's no difference. Our local yielded property is shadowing the global HTML input.
If you always use CapitalCase to refer to Ember components, you'll never run into this problem. In fact, you'll even be able to use global HTML elements alongside your Ember components, no matter the context:
<Forms::Label as |Input|>
I am a label
<Input placeholder='Ember component' />
<input placeholder='HTML element' />
<Forms::Label>
So, that will likely be the common practice in the ecosystem going forward.
As one concluding point, note that angle bracket invocation syntax is only concerned with invocation, not authorship. It doesn't matter how the underlying component is implemented, whether it's a fancy Ember component on a recent version or an older one using older APIs from an addon. Angle bracket invocation works as long as your app is on a recent version of Ember, or is using the Angle Bracket Invocation Polyfill.
And finally, be sure to check out the Ember Angle Brackets Codemod. It will convert older curly-style syntax to new angle-bracket syntax for you automatically!