Are you curious about how ngOnChanges works and when to use it?
Well, then you've come to the right place!
In this comprehensive guide to Angular's ngOnChanges
lifecycle hook, we'll explore how it works and why it's important, and I’ll explain how to use it effectively in your Angular applications.
I’ll even compare it against some other lifecycle hooks so that by the end of this guide, you'll feel far more confident in using ngOnChanges
to make your components more robust and responsive.
So grab a coffee and a notepad and let's dive in!
Sidenote: If you want to dive deeper into Angular, as well as all of its lifecycle changes, and more, then check out my complete Angular Developer course.
Fully updated and covering the latest Angular features for 2024, I guarantee that this is the most comprehensive online course on Angular.
You’ll also build confidence by creating real-world projects (including a massive video-sharing application similar to the example in this guide) step-by-step alongside me, Senior Developer!
With that out of the way, let’s get into the guide!
ngOnChanges
is one of the multiple lifecycle hooks available in Angular.
This particular hook gets called when any data-bound input property changes. It then notifies you when an input property changes and provides the current and previous values, allowing you to understand and react to these changes effectively.
For example
Suppose you have an interface component displaying a user's profile information.
If the user updates their profile picture, Angular detects this change and triggers ngOnChanges
.
(Fun fact - Netflix was built on Angular).
And so within the ngOnChanges
function, you can write code to update the displayed picture, ensuring that the user sees their new profile image instantly.
This also makes it versatile for updating UI in sync with state and logging changes for debugging purposes!
So now we know what ngOnChanges is
and how it works, let's look at how to use it.
To use ngOnChanges
, you just need to implement the OnChanges
interface in your component and define the ngOnChanges
method.
(This method takes one argument, such as an object adhering to the SimpleChanges
interface, that contains the current and previous property values for each changed input property).
For example
Here's a basic implementation:
import { OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnChanges {
@Input() data: any;
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let change = changes[propName];
let curVal = JSON.stringify(change.currentValue);
let prevVal = JSON.stringify(change.previousValue);
console.log(`${propName}: currentValue = ${curVal}, previousValue = ${prevVal}`);
}
}
}
What's happening here?
Well, in the ProfileComponent
, we have a single @Input()
property called name
.
And so if the parent component changes the name, Angular calls the ngOnChanges
method with a SimpleChanges
object.
For example
If you were to change the name
from 'John' to the name of our glorious leader 'Bruno', then the ngOnChanges
method logs the following message:
name: currentValue = "Bruno", previousValue = "John"
It’s a little complicated in that ngOnChanges
is called both before and after change detection, depending on the context.
Angular calls ngOnChanges
at two specific points:
ngOnInit
and after the first ngOnCheck
For example
When a component is first being created, Angular goes through the following sequence of lifecycle hooks:
ngOnInit
ngOnChanges
. At this point, the component is fully initializedngOnInit
and then every subsequent check of the componentThis means that after the component is fully initialized and whenever Angular's change detection runs, it checks for changes in the values of properties and bindings. If it detects changes, it updates the view to match the current state of the component and then triggers ngOnChanges
.
So like I said, in a way, ngOnChanges
can come both before and after change detection.
We mentioned a few of the other lifecycle hooks above, so let’s look at a few and compare them to ngOnChanges
.
ngOnChanges
is called before ngOnInit
during the component initialization phase. After that, ngOnChanges
is called every time an input property changes, while ngOnInit
is only called once.ngOnInit
is used for component initialization work, like calling a service to fetch data. ngOnChanges
is used to perform actions in response to input property changes.**For example:**sing ngOnInit
for initialization
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-data-fetcher',
template: `<div>{{ data }}</div>`
})
export class DataFetcherComponent implements OnInit {
data: string;
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.fetchData().subscribe(fetchedData => {
this.data = fetchedData;
});
}
}
For example: Using ngOnChanges
to react to input changes
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user-profile',
template: `<h2>{{ user.name }}</h2>`
})
export class UserProfileComponent implements OnChanges {
@Input() user: { name: string, age: number };
ngOnChanges(changes: SimpleChanges) {
if (changes['user']) {
const currentValue = changes['user'].currentValue;
const previousValue = changes['user'].previousValue;
console.log('User changed from', previousValue, 'to', currentValue);
}
}
}
For example: Using ngDoCheck
for custom change detection
import { Component, DoCheck, Input, KeyValueDiffers, KeyValueDiffer } from '@angular/core';
@Component({
selector: 'app-settings',
template: `<div>Settings for {{ settings.theme }}</div>`
})
export class SettingsComponent implements DoCheck {
@Input() settings: { theme: string, notifications: boolean };
private differ: KeyValueDiffer<string, any>;
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
ngDoCheck() {
const changes = this.differ.diff(this.settings);
if (changes) {
changes.forEachChangedItem(item => {
console.log('Changed item:', item.key, 'currentValue:', item.currentValue, 'previousValue:', item.previousValue);
});
}
}
}
ngOnChanges
to react to changes in input properties that may occur multiple times. If your component receives multiple input properties and requires complex calculations whenever any of these properties change, ngOnChanges
is preferablengOnInit
for initialization logic that relies on set input properties and needs to run once per component instantiationngDoCheck
when you want to perform an action during every change detection cycle, regardless of whether any changes were detectedAs you can see, using ngOnChanges
can be incredibly useful for detecting and reacting to changes in input properties.
However, for simpler or more performance-sensitive scenarios, then other Angular features might be more appropriate.
For properties that change very frequently (e.g., every few milliseconds), using ngOnChanges
can lead to performance issues due to the high frequency of method calls.
Instead, consider using RxJS observables with the async
pipe in your templates. This approach can be more efficient because Angular's change detection can be more finely controlled and optimized.
import { Component, Input, OnInit } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-stock-ticker',
template: `<div>Current Price: {{ price$ | async }}</div>`
})
export class StockTickerComponent {
@Input() stockSymbol: string;
price$: Observable<number>;
ngOnInit() {
this.price$ = interval(1000).pipe(
map(() => this.fetchStockPrice(this.stockSymbol))
);
}
fetchStockPrice(symbol: string): number {
// Simulate fetching stock price
return Math.random() * 100 + 100;
}
}
For simple scenarios where you need to react to changes in input properties to compute a value or trigger a side effect, using a getter and setter for the input property might be sufficient and more straightforward than implementing ngOnChanges
.
This approach allows you to handle changes immediately in the setter method.
private _inputValue: string;
@Input()
set inputValue(value: string) {
this._inputValue = value;
this.doSomething(value);
}
get inputValue(): string {
return this._inputValue;
}
doSomething(value: string) {
// React to the change
console.log('Input value changed to', value);
}
Hopefully this guide has opened up your eyes to some possibilities of how you might use this hook in your own projects.
Getting a handle on the ngOnChanges
lifecycle hook in Angular is key for making your apps more dynamic and responsive.
By getting comfortable with ngOnChanges and other Angular hooks, you'll be able to make your apps faster and more efficient!
Remember, if you want to dive deeper into Angular and have a structured learning process that can take you from complete beginner to getting hired, then check out my complete Angular Developer course.
Once you join, you’ll also have access to every other course in the Zero To Mastery library, as well as access to the private Discord.
You can ask questions from me, as well as chat with fellow students and working Angular Developers!