Overview

If you have a mixin which is applied to legacy classes, where the mixin uses an observer to trigger an action on those legacy classes, you should:

  1. Run the native classes codemod on all legacy classes affected.
  2. Convert the mixin usage to a “manager”-style/data-only component.

Table of Contents

Walkthrough

I. Starting point

Let’s imagine a hypothetical component which supplies a counter when you’re nearing the limits on a text input, by observing a value property on a text editing component and calling an action . We’ll call it TextCountdown. It’s intended to be mixed into a class which extends Ember’s native Textarea component.

// app/mixins/text-countdown.js
import { once } from '@ember/runloop';
import { observer } from '@ember/object';
import Mixin from '@ember/object/mixin';
import { isNone } from '@ember/utils';

export default Mixin.create({
  // required arguments
  maximum: undefined,

  // optional arguments
  showCountdownAt: 10,
  countdownLimit: 0,

  onValueChange: observer('value', function() {
    once(this, 'valueDidChange');
  }),

  valueDidChange() {
    const remaining = this.maximum - this.value.length;
    const showCountdown = remaining <= this.showCountdownAt;
    const limitReached = remaining <= this.countdownLimit;
    this.updateCountdown(remaining, showCountdown, limitReached);
  },
});

Note the implicit dependencies of this mixin, which could be exposed by documentation, but are invisible when looking at the consuming class:

The class you mix it into might look something like this:

// my-app/components/custom-textarea.js
import Textarea from '@ember/component/textarea';
import TextCountdown from 'my-app/mixins/text-countdown';

export default Textarea.extend(TextCountdown, {
  // Set a required property for the mixin
  maximum: 200,

  // Override the values from the mixin
  showCountdownAt: 20,
  countdownLimit: -1,
});

Using the component in a template then looks something like this:

<!-- my-app/templates/components/custom-textarea.hbs -->
<CustomTextarea
  @value={{this.value}}
  @updateCountdown={{this.setNewCountdownValues}}
/>

Note that the CustomTextarea here is a template-less component: all of its functionality is accomplished by extending two other classes in JavaScript.

II. Running the native class codemod

The first thing we’re going to do is transform this to using native classes, using ember-native-class-codemod. (Note that you will need to have followed the codemod's setup instructions for this to work.)