The most common Ember.js Octane mistakes and how to avoid them

Jen Weber
Ember-ish
Published in
6 min readDec 21, 2019

A month ago, I asked some Ember.js redditors about the most common mistakes they and their teams made while writing Ember apps using Octane, which is a word to describe the latest and greatest way to write Ember apps. Today, we’ll cover what those mistakes were and how to avoid them!

To learn what Octane is in the first place, check out the release blog post.

This is a lot to remember, so keep a bookmark to the Octane Cheat Sheet handy for the future.

This article is like the opposite of Fantastic Beasts and Where To Find Them. (Photo by Artem Maltsev on Unsplash)

Not reading the linting errors

A lot of the most common mistakes in Ember can be avoided by making good use of Ember’s linting errors. Having an up-to-date ember-template-lint and eslint-plugin-ember installed in your app can make a huge difference in catching errors for you, along with looking at the browser developer console while clicking through your app, and also paying attention to the output when you run ember serve. There are at least 10 really common mistakes I made while learning that we don’t have to cover here thanks to these linters, such as forgetting to mark something as tracked.

If you are working with a new Ember dev who is struggling, checking to make sure that they can see linting errors in their code editor, browser console, and terminal is the first step to helping out.

Overlooking where a component imports from

When you are working with an Octane style component, it imports from @glimmer/component instead of @ember/component.

These two types of components have different APIs and behaviors. There’s not a one-to-one match. Compare the API documentation for “classic” components vs Octane-style Glimmer components. The most confusion happens when new Ember devs stumble across old blog posts and code snippets, without realizing that those examples don’t really apply to modern Ember.

If you’re working on an existing Ember app, you’ll probably see both types of components for a while. If you want to read about the comparisons or are wondering which you should use, check out the Upgrade Guide. For day-to-day work, use the Classic vs. Octane Cheat Sheet.

Component property scope

Here, we’re talking about the difference between this.args.something vs this.something.

When you are working with an Octane style component, which imports from @glimmer/component instead of @ember/component, you need to pay close attention to where the component properties are coming from.

If you pass a property into a component like this:

<MyComponent @charity="350.org" />

When you are in the my-component.js file, you refer to the passed-in argument using this.args :

import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class MyComponent extends Component { @action
clickedAButton() {
console.log(this.args.charity) // this is "350.org"
console.log(this.charity) // this is undefined
}
}

The properties that belong to the MyComponent Class directly are referred to by this.somePropertyName:

import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class MyComponent extends Component {anotherCharity = "The Water Project";@action
clickedAButton() {
console.log(this.args.charity) // prints "350.org"
console.log(this.charity) // prints undefined
console.log(this.anotherCharity) // prints "The Water Project"
console.log(this.args.anotherCharity) // prints undefined
}
}

Learn more in the Upgrade Guide and the main guides section on Component Arguments.

Trying to use didInsertElement and other classic lifecycle hooks

When a component is imported from @glimmer/component, it doesn’t have the same lifecycle hooks as classic components, like didInsertElement, didUpdateAttribute, etc.

All you get out-of-the-box are constructor and willDestroy, as you can see in the API documentation.

So, what if you needed that didInsertElement? I used it very often! Install ember-render-modifiers and start using the {{did-insert}} and other lifecycle modifiers in your templates.

Learn more about Template Lifecycle, DOM, and Modifiers in the Guides, or read what the Upgrade Guide has to say.

Trying to use this.element

A component imported from @glimmer/component doesn’t have a this.element. It will just show up as undefined. The reason is that with one of these new components, you can choose any wrapping element you want! No need for tagName and classBindings anymore. But, it does mean that you need to look up whatever element you are using as a wrapper, if there is one.

For the most part, you can use ember-render-modifiers instead, since they pass the target element to the function as the first argument:

<div {{did-insert this.doAnimation}}>

You get the element below:

import Component from '@glimmer/component';
import animate from 'some-animation-library-you-use'
export default class MyComponent extends Component { doAnimation(element) {
animate(element)
}
}

You can also install ember-modfier and write your own modifer, if you prefer.

In other cases, like for managing focus, you can make your own element id. Or, you can install and use addons like ember-ref-modifier, as described in the Guides.

Forgetting to set defaults for component arguments

I made this mistake a few times. I wrote some components with optional actions that you could pass in, like a validate function to check a form input. But if those components don’t have a default fallback action, they will cause a type error when the code runs, like this.args.validate is not a function:

Problem code that is prone to a type error:

import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class MyComponent extends Component {
@action
submitForm() {
let isValid = this.args.validate(this.formContents) // THIS IS BRITTLE!
if (isValid) {
this.postForm(formContents)
}
}
@action postForm() {
// some code that posts the form
}
}

Your choices are, you can either do an “if” check to see if the action exists:

if (this.args.validate) {
this.args.validate(formContents)
}

Or, what I prefer is using a getter to set a default:

import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class MyComponent extends Component { get validate() {
return this.args.validate || () => { true };
} @action
submitForm() {
// note that now we are using this.validate,
// not this.args.validate!
let isValid = this.validate(this.formContents)
if (isValid) {
this.postForm(formContents)
}
}
@action postForm() {
// some code that posts the form
}
}

Not installing the necessary dependencies

Ember is moving towards a place where you install the dependencies you need, and leave out the ones you don’t. This means that sometimes, what you read about in the Guides or blog posts are things that you need to explicitly install in your app! Here’s a short list of some of the optional, but common addons in Octane:

Skipping over learning Native JavaScript Classes

Octane makes such heavy use of object-oriented programming and JavaScript Classes that it’s really important to at least review what a normal, non-Ember-y JavaScript class is like. Check out the docs on MDN. A lot of things become clearer when you have this foundation.

Did I miss anything?

If I did, can you let me know? Or better yet, write your own blog post, share on Stack Overflow , or write on the Discuss forums to add to the growing, publicly searchable collection of Octane Q&A.

If you think your common mistakes are due to a gap in the learning materials, you can open an issue or PR for the Ember Guides repository.

Happy coding :)

--

--

Jen Weber
Ember-ish

Huge fan of Ember.js, open source, HTML, and accessible, inclusive web apps. www.jenweber.me