🔁 Moving Beyond ngOnChanges(): Why effect() is the Future in Angular 16+

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

FeaturengOnChanges()effect() + Signal
Reactivity TypeImperativeDeclarative
Requires @Input()YesNo
Tracks previous valueYes (via SimpleChanges)No (track manually if needed)
Clean-upManual (lifecycle)Automatic
BoilerplateHighMinimal

💡 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.

Leave a Comment

Your email address will not be published. Required fields are marked *