Skip to content

Instantly share code, notes, and snippets.

@Joelkang
Last active April 9, 2024 11:26
Show Gist options
  • Save Joelkang/dd7f3f54dc2f1115526dbca001853dbe to your computer and use it in GitHub Desktop.
Save Joelkang/dd7f3f54dc2f1115526dbca001853dbe to your computer and use it in GitHub Desktop.

Introduction

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.

History

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
});
{{input type="text" value=myValue}}
  1. This will output <input type="text" value"5"/>.
  2. 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.

One-way Bound Inputs

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);
    }
  }
});
<input type="text" value={{readonly myValue}} onchange={{action "updateMyValue" value="target.value"}} />

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.

What The Mut

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
});
<input type="text" value={{readonly myValue}} onchange={{action (mut myValue) value="target.value"}} />

Under the hood

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:

  1. The code block below means "pass the value of baz into {{x-foo}} as an attr called bar". This also makes it clear that you expect {{x-foo}} to be able to change the value of baz at some point.
{{x-foo bar=(mut baz)}}
  1. This code block means "create a closure action that sets the value of baz, and pass this action to {{x-foo}} as an attr called bar". This way, inside of the x-foo component, you can do this.sendAction('bar', 'newValueForBaz');.
{{x-foo bar=(action (mut baz))}}
  1. The closure-action syntax allows you to bind arguments to the mutator as well. This way, when you do this.sendAction('bar') inside of the x-foo component, it will set baz to "newValueForBaz". This is particularly helpful when you're iterating through a list.
{{x-foo bar=(action (mut baz) "newValueForBaz")}}

Consider this code block:

{{#each children as |child|}}
  {{child-display updateParentProperty=(action (mut parentProperty) child)}}
{{/each}}

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:

{{! templates/components/parent-component.hbs}}
{{#each children as |child|}}
  {{child-component 
    isSelected=(eq childSelected child)
    select=(action (mut childSelected) child)
  }}
{{/each}}
// components/child-component.js
import Ember from 'ember';
export default Ember.Component.extend({
  click() {
    this.sendAction('select');
  }
});
@cluis13915
Copy link

👍

@SandeepJoel
Copy link

SandeepJoel commented Sep 21, 2020

Wonderful Explanation! This article made my day! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment