Specifying a Default Value for an Argument

The Octane vs Classic Cheat Sheet describes how to set a default value for an argument:

import Component from '@glimmer/component';
export default class MyComponent extends Component {
  get avatar() {
    return this.args.avatar || 'default';
  }
}

This works for most situations, but it doesn't work when a valid value for the argument is falsey and the default value should not be applied.

For example, the component below has a @showContact argument that should default to true if its value is not passed in:

import Component from '@glimmer/component';
export default class MyComponent extends Component {
  get showContact() {
    return this.args.showContact || true;
  }
}

But when you call this component with <MyComponent @showContact={{false}} />, this.showContact is true instead of false.

For this situation, this.args needs to be checked if it contains the showContact property:

import Component from '@glimmer/component';

export default class MyComponent extends Component {
  get showContact() {
    if ('showContact' in this.args) {
      return this.args.showContact;
    } else {
      return true;
    }
  }
}

Even better, if you're using the Babel transform for nullish coalescing, you can simplify this back to something like the original, but robustly supplying the fallback only for undefined or null values:

import Component from '@glimmer/component';

export default class MyComponent extends Component {
  get showContact() {
    return this.args.showContact ?? true;
  }
}

Validating arguments

In Classic components, it was common to validate essential arguments in didReceiveAttrs, to guarantee the caller always passed valid values. Glimmer components do not have this hook! You might be tempted to use the {{did-insert}} or {{did-update}} modifiers from ember-render-modifiers. Don't! Instead, validate at the place the argument is consumed. This makes sure you do not eagerly evaluate the arguments—only when they're actually used.

In some components, this won't make a huge difference, but it makes a significant difference for cases where there are options for how the component is rendered. For example, if we were conditionally rendering a user's avatar only if the caller explicitly opted into showing it, and we were also doing some work to compute what the avatar should be, like so:

import Component from '@glimmer/component';
import { buildAvatarUrl } from 'my-app/utils/urls';

export default class Profile extends Component {
  get avatar() {
    return buildAvatarUrl(this.args.avatar);
  }
}
<div class='profile'>
  <p class='username'>{{@name}}</p>
  {{#if @showAvatar}}
    <img src={{this.avatar}} alt='user profile picture' class='avatar' />
  {{/if}}
</div>

Here, we could validate the @avatar arg two ways. First, we could do it with {{did-render}} and {{did-update}}:

import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { buildAvatarUrl } from 'my-app/utils/urls';

export default class Profile extends Component {
  validateAvatar(avatar) {
    assert('`@avatar` must be a string', typeof avatar === 'string');
  }

  get avatar() {
    return buildAvatarUrl(this.args.avatar);
  }
}
<div
  class='profile'
  {{did-insert validateAvatar @avatar}}
  {{did-update validateAvatar @avatar}}
>
  <p class='username'>{{@name}}</p>
  {{#if @showAvatar}}
    <img src={{this.avatar}} alt='user profile picture' class='avatar' />
  {{/if}}
</div>