Reactivity
Aurelia's reactivity system automatically tracks changes to your data and updates the UI efficiently. Unlike frameworks that use virtual DOM, Aurelia observes your data directly and surgically updates only what has changed.
When to Use Which Reactivity Feature?
Aurelia offers several reactivity tools. Here's how to choose:
Use simple properties (no decorator) when:
✅ You only need UI updates - Properties bound in templates are automatically observed
✅ Most common case - Just declare the property and bind it
✅ Example:
todos: Todo[] = []withrepeat.for="todo of todos"
Use getters (computed) when:
✅ Value depends on other properties and calculation is cheap
✅ Automatic dependency tracking - no manual configuration needed
✅ Example:
get fullName() { return this.firstName + ' ' + this.lastName; }
Use @computed decorator when:
@computed decorator when:✅ Expensive calculations that should be cached
✅ You want to explicitly control dependencies (not automatic)
✅ Deep observation needed for nested objects
✅ Example: Complex filtering, heavy aggregations
Use @observable when:
@observable when:✅ You need to run code when a property changes (side effects)
✅ You want the
propertyChanged(newValue, oldValue)callback✅ Examples: Validation, analytics tracking, syncing data
Use watch() when:
watch() when:✅ Complex expressions - watching multiple properties or nested values
✅ Need more flexibility than
@observable✅ Examples:
@watch('user.address.city'),@watch(vm => vm.total > 100)
Use manual observation when:
✅ Building libraries or advanced features
✅ Need fine-grained control over subscription lifecycle
✅ Performance critical code requiring optimization
Automatic Change Detection
Aurelia automatically observes properties that are bound in your templates. No decorators or special setup required:
The ${todos.length}, value.bind="filter", and repeat.for="todo of todos" create automatic observation - Aurelia tracks changes to these properties and updates the UI accordingly.
Computed Properties
Getter properties automatically become reactive when their dependencies change:
Decorator computed
computedFor some reason, it's more preferrable to specify dependencies of a getter manually, rather than automatically tracked on read, you can use the decorator @computed to declare the dependencies, like the following example:
You can also specify multiple properties as dependencies, like the following example:
Basides the above basic usages, the computed decorator also supports a few more options, depending on the needs of an application.
Flush timing with flush
flushLike how you can specify flush mode of computed getter with @computed({ flush: 'sync' }), flush mode of @computed can also be done in a similar way, like the following example:
Deep observation with deep
deepSometimes you also want to automatically observe all properties of an object recursively, regardless at what level, deep option on the @computed decorator can be used to achieve this goal, like the following example:
Now whenever _cart.items[].price or _cart.items[].quantity (or whatever else properties on each element in the items array), or _cart.gst changes, the total is considered dirty.
[!WARNING]
deepobservation doesn't observe non-existent properties, which means newly added properties won't trigger any changes notification. Replace the entire object instead.
Deep Observation
Aurelia can observe nested object changes:
Array Observation
Arrays are automatically observed for mutations:
When You Need @observable
The @observable decorator is only needed when you want to react to property changes in your view-model code (not just the template):
Effect Observation
Create side effects that run when observed data changes:
Manual Observation Control
For advanced scenarios, manually control observation:
Performance Considerations
Aurelia's observation is highly optimized:
Batched Updates: Multiple changes are batched into single DOM updates
Surgical Updates: Only changed elements are updated, not entire component trees
Smart Detection: Observes only bound properties, not entire objects
No Virtual DOM: Direct DOM manipulation eliminates virtual DOM overhead
Common Reactivity Patterns
Pattern: Form Validation with @observable
Use case: Validate input as the user types, show errors immediately.
Why this works: @observable triggers validation automatically as users type. The isValid getter recomputes whenever errors change, enabling/disabling the submit button reactively.
Pattern: Computed Filtering and Sorting
Use case: Filter and sort a list based on user input without re-fetching data.
Why this works: The filteredProducts getter automatically recomputes when any dependency changes. No manual refresh needed - the UI stays in sync with filters.
Pattern: Syncing Data with @watch
Use case: Keep related data in sync, like saving to localStorage or syncing with a server.
Why this works: @watch observes both content and title, automatically saving changes. The pattern prevents data loss and provides user feedback.
Pattern: Dependent Computations
Use case: Chain computed properties where one depends on another.
Why this works: Computed properties automatically form a dependency chain. When subtotal changes, discount updates, which updates afterDiscount, then tax, and finally total. All cascade automatically.
Pattern: Optimized List Updates with @computed
Use case: Expensive computations on large lists that should only recalculate when necessary.
Why this works: @computed with explicit dependencies prevents unnecessary recalculations. Changing individual data point properties won't trigger recalculation - only changes to array length, date range, or metric do.
Best Practices
Choose the Right Tool
✅ Start simple - Use plain properties and getters first
✅ Add
@observableonly when you need side effects✅ Use
@computedfor expensive operations, not simple getters❌ Don't over-engineer - most scenarios don't need
@watchor manual observation
Keep Computations Pure
✅ Computed getters should have no side effects
✅ Same inputs should always produce same outputs
❌ Don't modify state inside getters
❌ Don't make API calls in computed properties
Optimize Performance
✅ Use
@computedwith explicit dependencies for expensive calculations✅ Debounce rapid changes (user input, scroll events)
✅ Batch related updates together
❌ Don't create unnecessary watchers
Handle Async Operations
✅ Use
@watchorpropertyChangedcallbacks for async side effects✅ Track loading states during async operations
✅ Handle errors gracefully
❌ Don't make computed properties async
What's Next
Learn about observing property changes in detail
Explore effect observation for advanced reactive patterns
Understand watching data strategies
Last updated
Was this helpful?