- Start Date: 2015-02-27
- RFC PR:
- Ember Issue:
Introduce Ember.Fragment
and the <element>
and <fragment>
template keywords.
This RFC addresses two key problems:
-
Ember has no clear concept of a "tagless component". We need such a concept now that we're moving to a component-centric world.
-
Today users are forced to learn two separate APIs for setting element attributes, one that works only for a component's top-level element (and cannot be used from inside templates), and one that works everywhere else.
Ember.Fragment
is introduced to represent "tagless
components". Every Fragment's template must be defined with the
<fragment>
keyword.
Ember.Component
continues to only represent components with
tags. This is consistent with a web-component world. Every Component's
template must be defined with the <element>
keyword.
If you place this template in my-component.hbs
:
It can be invoked like this:
And will result in this output:
<div class="special magical" data-my-id="1" id="1" name="Tomster">
Hello Tomster.
</div>
<element>
accepts an optional tagName
, so if we changed the
template to:
The output changes to
<span class="special magical" data-my-id="1">
Hello Tomster.
</span>
There are three places that may legitimately need to set a component's HTML attributes:
-
The caller of a component may intend one of more of the provided attributes to go directly onto the HTML element:
<my-component title="Tomster" model=hamster>
Every attribute passed into a component in this way gets set on the component's
attrs
object, which is how components receive arguments in general. Any attribute with a primitive value (string, number, boolean) also gets set by default as an HTML attribute on the component's element. So in the example above,title="Tomster"
will appear by default on the component's element, butmodel
will not, assumingmodel
is anobject
. -
The component itself may set its own HTML attributes:
<element title="Mr {{attrs.title}}">
This is logically equivalent to calling
setAttribute
directly on the component's element in thedidInsertElement
hook, and setting up a corresponding observer to keep the value up to date. It takes precedence over any values set by the caller.It is important to understand that
<element>
only sets HTML attributes, it does not setattrs
. So the above example leaves the original caller providedattrs.title
alone, but overrides thetitle
HTML attribute to have a modified value. -
The component's parent class may also have attribute settings in its
<element>
that should be inherited. A child components takes precedence over its parent component's attributes.
So we can summarize the complete semantics as this series of logical steps:
-
First, any caller-provided attributes with primitive values are set as HTML attributes.
-
Next, the component asks its parent class to set HTML attributes based on what appears in the parent's
<element>
. This recurses back so that the most primitive ancestor is setting first, and descendants have the chance to override their ancestors. -
Finally, the component sets attributes based on its own
<element>
.
<fragment>
is very simple, it just declares that your template is a
fragment and not a component. It doesn't generate any output in the
DOM and it doesn't take any arguments. This:
Outputs:
This is a fragment.
Components in Ember 2.0 can be invoked with angle bracket syntax:
But fragments are always invoked with curlies:
This lets the reader of a template know exactly what they're dealing with.
They get deprecated.
Components need a dash for compatibility with web components. But this constraint does not apply to fragments, so fragment's names do not need to include a dash.
If we want to distinguish between Component templates and Fragment
templates, at least one of them needs to be mandatory. It makes sense
for <element>
to be the mandatory one, since <element>
does useful
work and represents a real DOM element.
<fragment>
is only needed due to upgrade-compatibility
concerns. Some day we'll be able to treat any component without an
<element>
as a Fragment by default, but right now that would alter
the behavior of existing components.
Therefore, templates with neither <element>
nor <fragment>
will
receive legacy behavior.
Components must extend Ember.Component
, the same as today.
Fragments must extend Ember.Fragment
.
Mismatching the class and the template will throw an exception at instantiation.
Ember.Fragment
is probably an ancestor of Ember.Component
, so that
it can define behaviors common to both.
Adds a tiny amount of arguable boilerplate to templates that was not there before.
Instead of <element>
, components could just use their own real tag
in its place. But this makes it hard to distinguish template
definition from template invocation.
We could avoid introducing <fragment>
and treat any template without
an <element>
as a fragment, but this makes the upgrade painful.
We could detect whether a template has a single top-level element or
not and automatically make it by a Component or Fragment. This idea
was rejected because it's a footgun -- for example, accidentally
changing your template from one kind to the other would change the
sematnics of your $
method.
-
Should
Ember.Component
extendEmber.Fragment
? -
Should
Ember.Fragment
have a$
method that matches its DOM range, or should we call it something different to distinguish it from theComponent
case, or leave it off entirely?