Choosing Ember Octane Native Class Syntax vs Classic Ember.Object for Components and Services
Decide whether to use Ember Octane native class syntax or classic Ember.Object for new components and services, with constraints, a comparison table, trade‑offs, and a step‑by‑step implementation example.
11 Jun 2026, 14:33 UTC

Decision and Constraints
Adopt Ember Octane native class syntax for new components and services when you are on Ember ≥ 3.13, have access to @glimmer/component and @glimmer/tracking, and want to use modern JavaScript features (ES6 classes, decorators) for better tooling and future‑proofing. If your project must stay on an older Ember version or the team lacks experience with ES6 classes and decorators, classic Ember.Object remains a viable, zero‑migration‑cost option.
Comparison of Supported Options
| Feature | Native Class (Octane) | Classic Ember.Object |
|---|---|---|
| Syntax | ES6 class with @tracked and @action (or plain methods) | extend(Ember.Object) |
| Reactivity | Built‑in @tracked properties | Manual get/set observers |
| Decorators | Requires ember-decorators addon for @action (optional in pure Octane) | N/A |
| Tooling | Better IDE autocomplete, TypeScript support | Limited |
| Migration Effort | May need codemods for existing classic code | No change required |
Trade‑offs
- Native class advantages: clearer structure, alignment with Ember’s roadmap, improved TypeScript integration, and ergonomic reactivity via
@tracked. - Native class drawbacks: learning curve for decorators, potential need to rewrite legacy mixins as utility functions or class decorators, and reliance on codemods for existing codebases.
- Classic advantages: fully supported, zero migration cost, works on any Ember version.
- Classic drawbacks: lacks modern ergonomics, may hinder future upgrades, and offers limited IDE/TypeScript support.
Concrete Implementation and Validation
Below is a step‑by‑step guide to create a component using native class syntax and verify it works.
1. Generate the component
Run this command in the project root (you need write access to the src or app directory):
ember generate component user-profile --no-test
This creates app/components/user-profile.js and app/components/user-profile.hbs using the native class blueprint (available in Ember ≥ 3.13).
2. Edit the component file
Replace the generated content with the following implementation:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class UserProfile extends Component {
@tracked name;
@tracked email;
updateName(event) {
this.name = event.target.value;
}
}
3. Update the template
Edit app/components/user-profile.hbs to bind the tracked properties and action:
<div>
<label>Name:</label>
<input value={{this.name}} oninput={{this.updateName}} />
<p>Hello, {{this.name}}!</p>
</div>
4. Validate the component
- Check Ember version:
ember -vshould output 3.13 or higher. - Inspect the file: open
app/components/user-profile.jsand confirm it uses an ES6 class and imports from@glimmer/componentand@glimmer/tracking. - Run the app in test mode:
ember test --serverlaunches the test runner; navigate to the component’s test (if you added one) or manually visithttp://localhost:4200/components/user-profile(if you expose it via a route) and verify that typing in the input updates the displayed name without console errors. - Production build check:
ember build --environment=productionshould complete without deprecation warnings about classic class usage.
Limitations and Practical Checks
If your project uses Ember < 3.13 or lacks @glimmer/component, the native class syntax will throw a runtime error; verify the version first. Teams that rely heavily on legacy mixins may need to convert those mixins to plain functions or class decorators before migration. After migration, run your existing test suite to ensure no behavior changed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.