ember: functions as helpers

2022-02-17

 | 

~3 min read

 | 

560 words

Recently I had to add a new setting to an application.

The settings are saved in a Redux store. When the page loads, I wanted to present the user with their settings. That way it’s clear how the application is configured to work.

The new setting I was adding had three possible, mutually exclusive states:

type NewSetting = "Option A" | "Option C" | "Option C"

type Settings = {
  oldSettingA: boolean
  newSetting: NewSetting
}

Since the new setting states are mutually exclusive, a group of radio buttons made sense:

template.hbs
<label for='Option A'>
  <input
    type='radio'
    name='new-setting'
    value='Option A'
    id='Option A'
    {{on 'change' this.handleChange}}
  />
  Option A
</label>
<label for='Option B'>
  <input
    type='radio'
    name='new-setting'
    value='Option B'
    id='Option B'
    {{on 'change' this.handleChange}}
  />
  Option B
</label>
<label for='Option C'>
  <input
    type='radio'
    name='new-setting'
    value='Option C'
    id='Option C'
    {{on 'change' this.handleChange}}
  />
  Option C
</label>

So far so good, except that this does not set a default.

Let’s look at the first approach I took and then dive into the solution I landed on.

Initial Approach: Multiple Getters

One way to handle this problem is to have a dedicated Getter on in the backing Javascript component for each option in the setting. This felt really duplicative. For example, right now I had three choices, but what happens if I need to add a new option? I’d need to write another function!

component.ts
type Args = {
  settings: Settings
}

export default class Settings extends Component<Args> {
  get isOptionA() {
    this.args.settings.newSettings == "Option A"
  }
  get isOptionB() {
    this.args.settings.newSettings == "Option B"
  }
  get isOptionC() {
    this.args.settings.newSettings == "Option C"
  }
}

Then, you could update the template:

template.hbs
<label for='Option A'>
  <input
    type='radio'
    name='new-setting'
    value='Option A'
    id='Option A'
    checked={{this.isOptionA}}
    {{on 'change' this.handleChange}}
  />
  Option A
</label>
<label for='Option B'>
  <input
    type='radio'
    name='new-setting'
    value='Option B'
    id='Option B'
    checked={{this.isOptionB}}
    {{on 'change' this.handleChange}}
  />
  Option B
</label>
<label for='Option C'>
  <input
    type='radio'
    name='new-setting'
    value='Option C'
    id='Option C'
    checked={{this.isOptionC}}
    {{on 'change' this.handleChange}}
  />
  Option C
</label>

This is… fine. It’s fine. It just feels like there should be a better way.

Better Approach: Plain Functions as Helpers

Fortunately, there is a better way!

Helpers! A helper is “just a function you can invoke from a template.” With the Functions as Helpers polyfill, we can get write our own helpers and get the dynamism I was looking for!

component.ts
type Args = {
  settings: Settings
}

export default class Settings extends Component<Args> {
  get isSelected(value: NewSetting) {
    this.args.settings.newSettings == value
  }
}

Then, you could update the template:

template.hbs
<label for='Option A'>
  <input
    type='radio'
    name='new-setting'
    value='Option A'
    id='Option A'
    checked={{this.isOption 'Option A'}}
    {{on 'change' this.handleChange}}
  />
  Option A
</label>
<label for='Option B'>
  <input
    type='radio'
    name='new-setting'
    value='Option B'
    id='Option B'
    checked={{this.isOption 'Option B'}}
    {{on 'change' this.handleChange}}
  />
  Option B
</label>
<label for='Option C'>
  <input
    type='radio'
    name='new-setting'
    value='Option C'
    id='Option C'
    checked={{this.isOption 'Option C'}}
    {{on 'change' this.handleChange}}
  />
  Option C
</label>

Wrap Up

Hopefully this little walk through helps the next time you need to have a dynamic helper! There’s also functions as modifiers (here’s the polyfill). While the functions as helpers has been merged and is nearly available for the latest version of Ember, the functions as modifiers is still going through the process.

Big thanks to NullVoxPopuli and the Ember Discord for pointing me in the right direction!



Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!