Using the @action and @tracked Decorators in Ember Classic Classes

Last reviewed on November 11, 2020

If you're in the midst of transitioning your classic classes (those that call .extend()) to native classes, it may be helpful to know that you can still use the @action and @tracked decorators in classic classes.

Using @action in Classic Classes

We can refactor our classic classes to use the @action decorator so that we can stop using the actions hash and the action helper and remove the quotes from our action names in our templates.

Before

import Controller from '@ember/controller';

export default Controller.extend({
  actions: {
    handleClick() {
      console.log('clicked');
    },
  },
});
<button type="button" onclick={{action "handleClick"}}>
	Click me
</button>

After

import Controller from '@ember/controller';
import { action } from '@ember/object';

export default Controller.extend({
  handleClick: action(function () {
    console.log('clicked');
  }),
});
<button type="button" {{on "click" this.handleClick}}>
	Click me
</button>

If you need to curry arguments, you can use the fn helper:

import Controller from '@ember/controller';
import { action } from '@ember/object';

export default Controller.extend({
  handleClick: action(function (foo, bar) {
    console.log('clicked', foo, bar);
  }),
});
<button type="button" {{on "click" (fn this.handleClick "foo" "bar")}}>
	Click me
</button>

Using @tracked in Classic Classes

We can refactor our classic classes to use the @tracked decorator so that we don't need to call this.set().

Before

import Controller from '@ember/controller';

export default Controller.extend({
  appName: 'Foo',
  actions: {
    handleClick() {
      this.set('appName', 'Bar');
    },
  },
});
<h1>Welcome to {{this.appName}}</h1>

<button type="button" onclick={{action "handleClick"}}>
	Click me
</button>

After

import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default Controller.extend({
  appName: tracked({ value: 'Foo' }),

  handleClick: action(function () {
    this.appName = 'Bar';
  }),
});
<h1>Welcome to {{this.appName}}</h1>

<button type="button" {{on "click" this.handleClick}}>
	Click me
</button>

If the initialization of your tracked property involves some logic like using a service as opposed to a static value, you can pass in an initializer function instead of value:

import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default Controller.extend({
  appName: tracked({
    initializer() {
      return this.store.peekRecord('setting', 1).siteName;
    },
  }),

  handleClick: action(function (foo, bar) {
    this.appName = 'Bar';
  }),
});