This is the third in a series of posts designed to share our experiences around developing Ember.js apps and the best practices that we’ve found while doing so. Check out Lin’s post on page objects and acceptance tests, or Estelle’s post on not leaking state into factories.
Closure actions
Ember 1.13 came with the release of improved actions.
A side effect of this feature is that it enables us to move away from
{{view 'select'}}
and simply use DOM <select>
elements with actions.
trouble with forms/form elements in ember?
Turn out in 1.13.3 you can just use "THE DOM"
http://t.co/9U73q0KOOE
— Stefan Penner (@stefanpenner)
July 7, 2015
His linked JSBin links to Ember canary and no longer works, but a clone modified to use 1.13 is available here.
You are probably familiar with the previous action bubbling semantics. Actions in a component are isolated within that component. Actions outside a component bubble first through the current controller, then through the route hierarchy.
The new approach of closure actions is simply to wrap this action up in a function and pass it around. Anyone can call this function later. And since the context is wrapped up in the function closure, the caller does not need to know where it came from.
For us, the important thing is that it enables us to bind actions
directly to elements, like this: <select onchange={{action "doThing"}}>
.
What’s wrong with {{view 'select'}}
?
Ember is moving away from two of the core concepts used by {{view 'select'}}
.
Views
The Ember team, as well as many people in the community building
Ember apps have discovered that the confusion around view scopes
has been a huge source of hassle and bugs. The explicit scoping of
components is much more maintainable. Thus, the Views section has been
removed from the Ember guides, and the {{view}}
helper has been
deprecated. Nowadays, you get one view to go with
your route, and otherwise View
serves simply as a superclass to
Component
. As we move towards routable components, even the routes’
views will begin to disappear.
Two-way data binding
Ember and the Ember ecosystem are also moving away from two-way bindings, and towards a Data Down, Actions Up architecture (DDAU).
Triggering an action includes context that is missing with simple data binding. Why is this value changing? Did we receive an update from the back-end, or did the user hit save? Lacking this context can lead to complicated conditional logic. Or, when combined with observers can cause an infinite loop hitting your back end.
It’s also hard to go back once you opt-in to two-way bindings. You’ve
hooked up a form field with data binding, and now you realize you need
validation on that field. Oh, you also need a confirmation when it is
valid (this is some high-stakes data). You’ll find yourself using an
observer, or splitting your value
property into previousValue
and
currentValue
properties. It is easier to hook in for these kinds of
things when triggering actions instead.
There’s also an issue of trust and boundaries. It turns out, “components want to be able to hand out data to their children without having to be on guard for wayward mutations.” Data flowing only in one direction keeps things more predictable.
Okigetit, how do I do it?
Well, Stefan’s JSBin I spoke about above is really a better example than I could give here. But, basically:
- Use a DOM
<select>
tag, and use an{{#each}}
to render option tags. - Bind an action to its
onchange
attribute. - Create an action handler to set the property value.
But what about {{input}}
?
Although {{input}}
behaves more like a component than a view, there
are similar issues around the data binding.
However, it turns out the same approach as above does not work quite as well with
<input>
tags. Because of how HTMLBars re-renders views, any time you
type a character, your cursor will be reset to the end of the text
field. This is awful! There’s a way around it, using the side effects of
readDOMAttr(). I won’t go into it here, but Rob Jackson
has put together a small example component using this technique.
May your forms be filled, your inputs valid, and your error template never render!
Note: This post has been edited. It originally recommended using
<input>
tags with actions. Because of the issues mentioned around
cursor position, it has been changed to discuss select
instead.