Built-in input helpers in Ember.js: when should they be used?
18 June 2019
I want to thank Robert Jackson and Stefan Penner for their discussions and guidance on the subject and for reviewing this blog post. Jan Buschtöns for ember-on-modifier
and Chris Garrett for his relentless work on Octane. And everybody else who makes this framework more awesome by the day.
With the arrival of angle-bracket syntax for component invocation for built-in Ember helpers, the question of whether it's good practice to use them has come to the fore again. I'll try to summarize the status quo as of Ember 3.10, in which the possibility of calling these helpers via angle-brackets (via <Input ...>
, <LinkTo ...>
, etc.) has landed.
Oh, cute, Ember can do checkboxes!
I didn't mean to do it, I really didn't.
I recently discovered you can have two-way bound checkboxes in Ember.js via the built-in {{input type="checkbox" checked=...}}
helper. I shared my discovery on Twitter which sparked a discussion on whether using two-way bound helpers in Ember is still justified sometimes.
Ember has a built-in checkbox component? Who knew? I certainly didn’t. https://t.co/oUMxtplIYv
— Balint Erdi (@baaz) June 4, 2019
Most tweets came from Robert Jackson and Stefan Penner who have both been contributing enormously to the Ember ecosystem and had somewhat differing views on the subject. To me, they were both really convincing so I was very happy to see they went on to record a video discussion that Rob then shared on YouTube:
I encourage you to watch the whole thing. I also added the summary of the video as a comment so that you can jump to any section you're interested in.
Below I'll first write the notes I took from the video and then distill the knowledge that I've extracted from it plus earlier blog posts, RFCs and comments.
I'm going to post my notes as a comment on the video, so if you plan to watch it, feel free to skip them and go to the conclusions (scroll down a bit).
Notes on video discussion
- (~3:20) The ability to add
<input onclick=... />
was added in Ember 1.13 but its use is not encouraged. - (~3:40) Just use on via the ember-on-modifier addon which has support for 2.18+ apps.
- (~4:00) Why we shouldn't use onclick={{action ...}}. Attributes vs. properties. SSR doesn't work well, with onclick={{...}}. The difference between props and attributes is really tricky, especially because attributes and properties with the same name can exist.
- (~4:45) In the Ember 1.13 era, Ember set properties for DOM elements, not attributes, to mitigate the security concern. However, properties cannot(?) be serialized and with the arrival of Fastboot that became a problem
- (~5:30) When you write HTML (not Handlebars), you write attributes (id=..., data-test=...). All attributes are strings. Ember bends the curve for handlebar templates.
- (~6:50) value in an input is a great example of the diff b/w attrs and props. The attr is the default ("server-rendered") value of the input, the value property is the current value.
- (~8:00) The on modifier Ember will gradually migrate to attribute-only setting in handlebars templates. The {{prop}} modifier exists (in an addon) if somebody really wants a property.
- (~9:00) The migration path to the attribute-only setting in templates.
- (12:00) Built-in helpers
- (12:30) There's an impedance mismatch between the HTML element and the corresponding Ember component which is really a burden on maintainers.
- (13:30) What value do BIHs provide that justifies their existence (maintenance). Why reinvent the DOM?
- (14:00) Rob: there are hard problems, like updating the value of an input upon re-rendering (it jumps to the beginning) that using the BIHs solves.
- (16:10) Rob'd like a helper (like mut) that encapsulates a property and a way to update the property so that you don't have to pass two properties down many layers. Today, we have to do "prop drilling", passing down the value and the function through all the layers.
- (18:40) Doesn't mut does that exactly? Right, but the problem with mut is that it returns a function that assigns its argument directly to the property you call it with. You usually need to peel off the event.target.value (or something similar).
- (21:00) Input is a "privileged" component that solves that prop drilling problem. It feels wrong that a built-in Ember helper can leverage this but a normal app developer cannot (the framework doesn't provide the tools).
- (22:00) This feels wrong, though, users should have access to framework primitives, so that should be exposed.
- (22:30) To summarize,
<Input>
is still needed today to get read/write access to a value you've been given ownership of. Today, there's no framework-provided way to achieve that same (you need an add-on or<Input>
). - (23:30) Input could be extracted from the core (and shipped by default, probably).
- (25:50) When should you use the native dom tags and when the built-in Ember helpers?
- (26:00) When you have a backing class, you should use the on modifier and DOM elements. If you have no backing class, you don't have a place to put the code that unwraps the event value (event.target.value, or sg. similar). So maybe what should be done here is to provide that unwrapping helper.
- (27:00) If you don't have a backing class and you need a simple way to read/write the value of an input, it's fine to use the built-in helpers (
<Input>
). - (28:30)
<Input>
and co. should only be used if there is no backing class to put the event handler on. The way we teach this is to use the on helper and native DOM tags. ons first argument is the event, the second argument is the callback so it's very similar to Element.addEventListener. - (29:15)
on
will land in 3.11! - on can also be "splattributed" in recent versions of Ember (in angle-bracket invocations)
- (~30:00) If you create a component for an input, you might as well provide some real value in that component, like adding a label to the input, providing more accessibility, and so on. If you do, you already have a backing class and you can use on.
- (30:20) You should almost always have a label for your input which means a component that wraps these two so you've now surpassed the simple use case (where
<Input>
would be sufficient) and should use a backing class, native input and on.
Conclusions
<input onclick=...>
works most of the time but doesn't work on svg elements and there's a minefield of problems (Rob's words), like resetting the cursor in the text box when re-rendering the value. (That particular bug was fixed in Ember 2.3.1, though.)- If you use HTML inputs (as opposed to the built-in Ember helpers) with an event handler and a value, you have to manage two distinct things: the value and the action to update that value. That's fine but it'd be great to reduce the friction.
- The built-in
{{input}}
component helps with those two issues but at the expense of a significant burden on the maintainers of that functionality. "Why reinvent the DOM?" is a valid argument here. - The
action
modifier is historically the way we do action handling in Ember. However, it has its own set of issues, as described by Chris Garrett: "What's up with @action"?. To see how complexaction
can be, I recommend Sam Selikoff's A note on actions. - Going forward, the action modifier is going to be replaced by the
on
modifier. You can already use it today via theember-on-modifier
add-on. It supports Ember versions 2.18+. - If you're starting an Ember app today, or one that has Ember 2.18+ and can justify the time used for converting
action
calls toon
calls, useon
. - You can either use
on
with theaction
function or use the newly RFC-edfn
helper. - Both
on
andfn
will arrive in Ember 3.11 so by the time you read this, you probably don't need to install the polyfills. - If you have the simplest of use cases, where you need to just read and update a value and you don't have a backing class of your template to define an event handler function in, it's totally fine to use the two-way built-in
<Input>
. - If you have a backing class, or you have more complex input event handling scenarios, use native inputs and
on
.
Having spent a good amount of time putting this together, I realize some concrete examples would be in order – I'll follow up with some in another blog post.
Share on Twitter