Moving Beyond Observers: Mastering Autotracking in Ember Octane
Stop manually managing dependencies in Ember. Learn how @tracked and autotracking eliminate the need for observers and computed properties to create high-performance, reactive UIs.
15 Nov 2025, 13:16 UTC

The Problem with Manual Dependency Tracking
In older Ember applications, keeping the UI in sync with data often required a complex web of computed properties and observers. The challenge was that developers had to explicitly declare every dependency. If you forgot to list a property in a computed property's dependent keys, the UI simply wouldn't update, leading to "ghost bugs" that were notoriously difficult to trace.
The takeaway is simple: modern Ember (Octane and later) removes the need to manually track dependencies. By using autotracking, the framework automatically detects which pieces of state are used during a render or a getter execution and updates only those specific parts of the DOM when the state changes.
How Autotracking Works
Autotracking relies on the @tracked decorator. When you mark a property as tracked, Ember wraps that property in a proxy. When a Glimmer component renders a template or executes a getter, Ember "records" every tracked property that was accessed during that process.
This creates a dynamic dependency graph. Instead of you telling Ember, "this property depends on X and Y," Ember observes, "I used X and Y to produce this output; therefore, if X or Y changes, I must re-run this specific logic." This shifts the burden of synchronization from the developer to the runtime.
Practical Implementation: A Reactive Counter
To implement autotracking, you need Ember Octane (version 3.13 or later). The following example demonstrates a basic counter where the UI updates automatically without any explicit observer or computed property declaration.
The Component Class
// app/components/counter.js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class CounterComponent extends Component {
@tracked count = 0;
// This getter is automatically tracked because it accesses 'this.count'
get doubleCount() {
return this.count * 2;
}
@action
increment() {
this.count += 1;
}
}
The Template
<div>
<p>Current Count: {{this.count}}</p>
<p>Double Count: {{this.doubleCount}}</p>
<button type="button" {{on "click" this.increment}}>Increment</button>
</div>
Verification and Execution
To test this behavior, run the following commands in your terminal from the project root:
ember serve: Starts the development server.- Open the browser and interact with the button. You should see both the
countanddoubleCountupdate instantly. - Diagnostic Check: Open the Ember Inspector browser extension. Navigate to the "Component" tab and select the Counter component. When you click the button, you can observe the property change in real-time without the entire component being destroyed and recreated.
Trade-offs and Limitations
While autotracking reduces boilerplate, it introduces a different set of challenges:
- Implicit Logic: Because dependencies are discovered at runtime, it can be harder to see at a glance exactly what triggers a re-render without using the Ember Inspector.
- Mutation Requirements: Autotracking only triggers when the tracked property itself is reassigned. If you mutate a property inside an object or array (e.g.,
this.user.name = 'New Name'), Ember will not detect the change unless theuserobject itself is marked as tracked or you replace the entire object. - Migration Overhead: Moving from "Classic" Ember to Octane requires refactoring observers into actions or tracked getters, which can be time-consuming in legacy codebases.
Actionable Closing
If you are still using didUpdateAttrs or observers to sync state, start migrating toward @tracked properties and getters. The performance gain comes from Glimmer's ability to perform fine-grained DOM updates, meaning only the specific text node containing {{this.count}} changes, rather than re-rendering the entire component tree. Start by identifying your most complex computed properties and converting them into simple JavaScript getters.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.