Writing · Ember.js

Ember Best Practices: Component inverse templates

Over the past couple of weeks I’ve been working on a dropdown menu component for the new Flood IO Dashboard, and I’d like to share with you an undocumented but very useful feature of Ember Components we’ve been using.

Originally published on Medium. Recovered from the Internet Archive and republished as written, so it reflects the tools and thinking of the time.

Over the past couple of weeks I’ve been working on a dropdown menu component for the new Flood IO Dashboard, and I’d like to share with you an undocumented but very useful feature of Ember Components we’ve been using.

When building a generalised dropdown component which has an easy to understand API, you quickly find yourself in need to being able to render two templates depending on the state of the dropdown. Typically one for the trigger and one for the dropdown container. Of course you could use a bunch of if statements to decide which template to render, but the API you end up exposing becomes pretty verbose.

{{!-- verbose-example.hbs --}}

{{#my-dropdown as |isOpen|}}
  <button onclick={{toggleOpen}}>Menu</button>
  {{#if isOpen}}
    <ol class="menu-items">
      <li>Item 1</li>
      <li>Item 2</li>
    </ol>
  {{/if}}
{{/my-dropdown}}

I don’t want the end user to have to specify an if statement every time they use my component, so to work around this we can specify a second template using the {{else}} statement. This becomes the inverse-template.

{{!-- verbose-example.hbs --}}

{{#my-dropdown as |isOpen|}}
  <ol class="menu-items">
    <li>Item 1</li>
    <li>Item 2</li>
  </ol>
{{else}}
  Menu
{{/my-dropdown}}

Then you can decide how you render each of these, but essentially in a dropdown use-case you will only yield one of them when isOpen is true.

{{!-- my-dropdown.hbs --}}

<div class="trigger">
  {{#if (hasBlock "inverse")}}
    <button onclick={{toggleOpen}}>{{yield to="inverse"}}</button>
  {{else}}
    <button onclick={{toggleOpen}}>{{label}}</button>
  {{/if}}
</div>

{{#if isOpen}}
  <div class="dropdown-container">
    {{yield}}
  </div>
{{/if}}

An added advantage is that we can make the inverse template optional, and fallback to the label property if we want, and abstract the trigger away to our component. This way we don’t leak any internal APIs to the end user.

Here’s an Ember Twiddle to explain this in practice.

This feature was introduced in Ember 1.13, so you should be okay to use it today. Documentation is however missing, so I can’t guarantee this API won’t change in the future. It works in Ember 2.2 though.