
With Angular 16 and newer (especially Angular 19), the framework has entered a new era of reactivity. The introduction of signals, computed, and effect() marks a shift away from traditional component lifecycle hooks. If you’ve been using ngOnChanges()
for detecting input changes, it’s time to consider whether effect()
might be a cleaner, more modern alternative.
Let’s break down what makes effect()
so powerful — and why it could replace ngOnChanges()
in many cases.
🧓 The Traditional Way: ngOnChanges()
ngOnChanges()
is a well-known lifecycle hook that runs when Angular detects changes to a component’s @Input()
properties. It gives you access to both the current and previous values via a SimpleChanges
object.
Example:
@Component({
selector: 'app-child',
template: '<p>{{ data }}</p>'
})
export class ChildComponent implements OnChanges {
@Input() data: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['data']) {
console.log('Data changed:', changes['data'].currentValue);
}
}
}
👎 Downsides of ngOnChanges()
:
- Tied tightly to
@Input()
- Requires extra boilerplate (
SimpleChanges
) - Doesn’t fit well into reactive or functional patterns
- Imperative by nature
🚀 The Modern Way: effect()
with Signals
Angular’s signals bring reactivity to the core of your components. The effect()
function reacts to signal changes, making your code cleaner, declarative, and easier to follow.
Example:
import { Component, effect, signal, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: '<p>{{ data() }}</p>'
})
export class ChildComponent {
private _data = signal('');
@Input() set data(value: string) {
this._data.set(value);
}
data = this._data;
constructor() {
effect(() => {
console.log('Data changed:', this._data());
});
}
}
No SimpleChanges
, no checks — just automatic reaction to changes.
✅ Why I Prefer effect()
Over ngOnChanges()
Here’s why I’ve started reaching for effect()
instead of ngOnChanges()
:
1. Less Noise, More Clarity
No more checking if changes['someInput']
exists. effect()
only cares about signals — and updates automatically.
2. Works Beyond @Input()
With signals, you can react to any state, not just input-bound properties.
3. Scoped Side Effects
The effect()
is automatically cleaned up when the component is destroyed — no need to manage subscriptions manually.
4. Better Composition
You can easily combine signals, computed values, and effects — making your code more modular and reactive.
5. Improved Debugging
It’s easier to follow data flow when effects are tied to specific signal changes — no more “what triggered this lifecycle?” confusion.
⚖️ When You Might Still Need ngOnChanges()
That said, ngOnChanges()
still has its uses — especially when:
- You’re working in a non-signal codebase
- You need to track multiple inputs and compare current vs previous values together
- You’re dealing with complex legacy components where introducing signals would require major rewrites
🔄 Quick Comparison
Feature | ngOnChanges() | effect() + Signal |
---|---|---|
Reactivity Type | Imperative | Declarative |
Requires @Input() | Yes | No |
Tracks previous value | Yes (via SimpleChanges ) | No (track manually if needed) |
Clean-up | Manual (lifecycle) | Automatic |
Boilerplate | High | Minimal |
💡 Final Thoughts
Angular 19 continues pushing toward cleaner, more reactive development patterns. effect()
is a big part of that transformation. If you’re building fresh components — or even refactoring older ones — I highly recommend giving effect()
a try.
You’ll write less code, get fewer bugs, and enjoy a more functional, signal-powered Angular experience.
Ready to ditch boilerplate and embrace the reactive future? Start swapping out ngOnChanges()
with effect()
and feel the difference.