This post covers an example of migrating a computed property to a native class getter and addresses a few questions that I had along the way.
Let's say we have the following controller as a classic class:
export default Controller.extend({
firstName: 'David',
lastName: 'Tang',
fullName: computed('firstName', 'lastName', function () {
return `${this.firstName} ${this.lastName}`;
}),
actions: {
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
},
},
});We can run ember-native-class-codemod to convert this classic class to a native class and get something like the following:
import Controller from '@ember/controller';
import { action, computed } from '@ember/object';
export default class ApplicationController extends Controller {
firstName = 'David';
lastName = 'Tang';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
}
}When we invoke the updateFirstAndLastName action, fullName recomputes.
Q: Do we need the @computed decorator?
When I first saw this, I wasn't sure how @computed worked. Would fullName recompute because it is now a getter? Let's remove it and see what happens.
import Controller from '@ember/controller';
import { action } from '@ember/object';
export default class ApplicationController extends Controller {
firstName = 'David';
lastName = 'Tang';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
}
}When we invoke the updateFirstAndLastName action, fullName doesn't recompute. Looks like we need the @computed decorator.
In order to remove the @computed decorator on fullName, we need to change firstName and lastName to be tracked properties via the @tracked decorator as follows:
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class ApplicationController extends Controller {
@tracked firstName = 'David';
@tracked lastName = 'Tang';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
}
}Now because we've changed firstName and lastName to be tracked properties, we no longer need set() or setProperties(). We can update firstName and lastName in our updateFirstAndLastName action using standard JavaScript assignment as shown below:
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class ApplicationController extends Controller {
@tracked firstName = 'David';
@tracked lastName = 'Tang';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateFirstAndLastName() {
this.firstName = 'James';
this.lastName = 'Aspey';
}
}Q: Will Getters Recompute When Dependent Getters Change?
We can also create getters that depend on other getters, and they'll recompute whenever the dependent getters change. For example, let's create a fullNameUpperCased getter that reads fullName.
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class ApplicationController extends Controller {
@tracked firstName = 'David';
@tracked lastName = 'Tang';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get fullNameUpperCased() {
return this.fullName.toUpperCase();
}
@action
updateFirstAndLastName() {
this.firstName = 'James';
this.lastName = 'Aspey';
}
}When we invoke the updateFirstAndLastName action, the fullNameUpperCased getter recomputes as fullName recomputes.
Q: Do We Need the @computed Decorator If We Are Relying on a Computed Property?
Let's say we have the following:
import Controller from '@ember/controller';
import { action, computed } from '@ember/object';
export default class ApplicationController extends Controller {
firstName = 'David';
lastName = 'Tang';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@computed('fullName')
get fullNameUpperCased() {
return this.fullName.toUpperCase();
}
@action
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
}
}Do we need @computed('fullName') on the fullNameUpperCased getter? It turns out we don't. In the code below, fullNameUpperCased will recompute when fullName recomputes.
import Controller from '@ember/controller';
import { action, computed } from '@ember/object';
export default class ApplicationController extends Controller {
firstName = 'David';
lastName = 'Tang';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get fullNameUpperCased() {
return this.fullName.toUpperCase();
}
@action
updateFirstAndLastName() {
this.setProperties({
firstName: 'James',
lastName: 'Aspey',
});
}
}Q: Can Computed Properties Rely on Getters?
Computed properties can't rely on getters. For example, fullNameUpperCased can't be a computed property that depends on the fullName getter. The following won't work:
import Controller from '@ember/controller';
import { action, computed } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class ApplicationController extends Controller {
@tracked firstName = 'David';
@tracked lastName = 'Tang';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@computed('fullName')
get fullNameUpperCased() {
return this.fullName.toUpperCase();
}
@action
updateFirstAndLastName() {
this.firstName = 'James';
this.lastName = 'Aspey';
}
}When we invoke the updateFirstAndLastName action, fullName will recompute but fullNameUpperCased won't.

