Many people are confused by the {{mut}}
helper because it seems very magical. This gist aims to help you construct a mental model for
understanding what is going on when you use it.
Prior to the introduction of {{mut}}
, form elements were two-way bound by default.
That is, given this component:
import Ember from 'ember';
export default Ember.Component.extend({
myValue: 5
});
- This will output
<input type="text" value"5"/>
. - Simultaneously, when you type into the textbox, the value of
myValue
will update automatically.
This is known as a two-way-binding because the <input>
's value
attribute is bound to myValue
for reads and writes. Two-way
bindings have historically created a lot of problems for UIs because it means that you can change the value in your textbox in one
place, and have the display in some other part of your app magically update. Some time this is useful to have, but when developers
don't intend on doing this, it can be painful to debug where changes are coming from.
In fact, you already are familiar with this if you understand Data Downs, Actions Up. The philosphy here is that the down
and up
are not magically coupled, but that there's one mechanism for down (via data), and another mechanism for up (via actions).
All the major Javascript frameworks have recognised this and have removed automatic two-way bindings in favor of explict declarations of two-way bindings. Two-way bindings are still very useful especially in forms, whose whole point is to display existing values and allow users to update those values using the form inputs and toggle.
Moving forward, Ember thus encourages (read: requires) you to
explicitly opt into two-way bindings by using the {{mut}}
and {{readonly}}
helpers declaratively in your templates. As a result,
developers can now drop the {{input}}
helper in favour of relying on plain ole HTML tags:
import Ember from 'ember';
export default Ember.Component.extend({
myValue: 5,
actions: {
updateMyValue(newValue) {
this.set('myValue', newValue);
}
}
});
In this example, the <input>
only reads myValue
into its value
attribute, and only writes new values via the onchange
event
handler: the updateMyValue
action. If you're confused about the value="target.value"
portion of the template, read about the value
parameter on the {{action}}
helper.
As you can see from the above example, this becomes increasingly tedious if you have a form with multiple fields and you need to
create a setter action for every single field which is basically this.set('myFieldName', value)
. Thus Ember provides the {{mut}}
helper as a short hand for doing this. The above codeblock can thus be re-written as
import Ember from 'ember';
export default Ember.Component.extend({
myValue: 5
});
It should now be obvious to you that the {{mut}}
helper thus returns some kind of function that the {{action}}
helper closes over.
When the change event is fired on the <input>
, the event handler then calls this function with the new value. Digging into the Ember
codebase, you'll see that the helper returns what is known as a Mutable Cell. A mutable cell in Ember is an object with a value and a setter:
let val = {
value,
update(val) {
source.setValue(val);
}
};
This allows the cell to be polymorphic depending on how it is used. Let's consider the different syntaxes:
- The code block below means "pass the value of
baz
into{{x-foo}}
as an attr calledbar
". This also makes it clear that you expect{{x-foo}}
to be able to change the value ofbaz
at some point.
- This code block means "create a closure action that sets the value of
baz
, and pass this action to{{x-foo}}
as an attr calledbar
". This way, inside of thex-foo
component, you can dothis.sendAction('bar', 'newValueForBaz');
.
- The closure-action syntax allows you to bind arguments to the mutator as well. This way, when you do
this.sendAction('bar')
inside of thex-foo
component, it will setbaz
to "newValueForBaz". This is particularly helpful when you're iterating through a list.
Consider this code block:
When one of the child-display
components does this.sendAction('updateParentProperty')
, the value of parentProperty
will be set
to that child. I've typically found this very helpful for doing things like closing all the dropdowns of a child except for the one
of the child who's now the value of updateParentProperty
. Another good use case is for selecting one of many:
// components/child-component.js
import Ember from 'ember';
export default Ember.Component.extend({
click() {
this.sendAction('select');
}
});
👍 That solved the mystery