ngOnChanges In Angular
In this article, we'll look at how Angular's ngOnChanges Life cycle hook works. When the Angular detects changes to the @Input attributes of the Component, it fires ngOnChanges or OnChanges. It receives the changes as a simple changes object. All of these will be covered in this article.
What is nOnChanges Life cycle hook?
The life cycle hook ngOnChanges angular fires when it detects changes to data-bound input properties. A SimpeChanges object is passed to this method, which contains the current and prior property values.
The parent component can communicate with the child component in a variety of ways. The @Input decorator is one of the options. In our article on providing data to Child Components, we looked at this.
Let's take a look back at what we learned in that article.
The @Input decorator is used by the child Component to adorn the property.
@Input() message: string;
The data is then passed to the child component by the parent using property binding, as demonstrated below.
<child-component [message]=message></child-component>`
How does it work
SimpleChange
Property Name | Description |
---|---|
previousValue:any | Previous value of the input property. |
currentValue:any | New or current value of the input property. |
FirstChange():boolean | Boolean value, which tells us whether it was the first time the change has taken place |
SimpleChanges
{ "message1": { "previousValue":"oldvalue", "currentValue":"newvalue", "firstChange":false } }, "message2": { "previousValue":"oldvalue", "currentValue":"newvalue", "firstChange":false } } }
If the input property is an object (for example, a customer object with a name and a code property), the SimpleChanges method is used.
{ "Customer": {"previousValue":{"name":"Angular","code":"1"}, "currentValue":{"name":"Angular2","code":"1"}, "firstChange":false} }
ngOnChanges example
export class Customer { code: number; name: string; }
Add the following code to the Parent Component.
import { Component} from '@angular/core'; import { Customer } from './customer'; @Component({ selector: 'app-root', template: ` <h1>{{title}}!</h1> <p> Message : <input type='text' [(ngModel)]='message'> </p> <p> Code : <input type='text' [(ngModel)]='code'></p> <p> Name : <input type='text' [(ngModel)]='name'></p> <p><button (click)="updateCustomer()">Update </button> <child-component [message]=message [customer]=customer></child-component> ` , styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ngOnChanges'; message = ''; customer: Customer = new Customer(); name= ''; code= 0; updateCustomer() { this.customer.name = this.name; this.customer.code = this.code; } }
<p> Message : <input type='text' [(ngModel)]='message'> </p> <p> Code : <input type='text' [(ngModel)]='code'></p> <p> Name : <input type='text' [(ngModel)]='name'></p> <p><button (click)="updateCustomer()">Update </button>
Using property binding, the message and Customer are connected to the child component.
<child-component [message]=message [customer]=customer></child-component>
The AppComponent class has two properties message and customer. When the user clicks the updateCustomer button, we update the customer object with the new code and name.
export class AppComponent { title = 'ngOnChanges'; message = ''; customer: Customer = new Customer(); name= ''; code= 0; updateCustomer() { this.customer.name = this.name; this.customer.code = this.code; } }
Now, add the following code to Child Component
import { Component, Input, OnInit, OnChanges, SimpleChanges, SimpleChange,ChangeDetectionStrategy } from '@angular/core'; import { Customer } from './customer'; @Component({ selector: 'child-component', template: `<h2>Child Component</h2> <p>Message {{ message }} </p> <p>Customer Name {{ customer.name }} </p> <ul><li *ngFor="let log of changelog;"> {{ log }}</li></ul> ` }) export class ChildComponent implements OnChanges, OnInit { @Input() message: string; @Input() customer: Customer; changelog: string[] = []; ngOnInit() { console.log('OnInit'); } ngOnChanges(changes: SimpleChanges) { console.log('OnChanges'); console.log(JSON.stringify(changes)); // tslint:disable-next-line:forin for (const propName in changes) { const change = changes[propName]; const to = JSON.stringify(change.currentValue); const from = JSON.stringify(change.previousValue); const changeLog = `${propName}: changed from ${from} to ${to} `; this.changelog.push(changeLog); } } }
import { Component, Input, OnInit, OnChanges, SimpleChanges, SimpleChange } from '@angular/core';
The message and name properties from the customer object are displayed in the Template. The parent component updates both of these properties.
template: `<h2>Child Component</h2> <p>Message {{ message }} </p> <p>Customer Name {{ customer.name }} </p>
We also use the ngFor Directive to show the changelog.
<ul><li *ngFor="let log of changelog;"> {{ log }}</li></ul> `
The life cycle hooks OnChanges and OnInit are implemented by the child Component.
export class ChildComponent implements OnChanges, OnInit { }
The @Input decorator is used to adorn the message and customer properties. Property Binding is used by the parent component to change these properties.
@Input() message: string; @Input() customer: Customer; changelog: string[] = [];
The OnInit hook is used to start a program.
ngOnInit() { console.log('OnInit'); }
The ngOnChanges hook receives all of the changes as a SimpleChanges object. For each property, this object contains an instance of SimpleChange.
ngOnChanges(changes: SimpleChanges) { console.log('OnChanges'); console.log(JSON.stringify(changes));
Then we gain a reference to the SimpleChange object by looping through each property of the SimpleChanges object.
for (const propName in changes) { const change = changes[propName]; const to = JSON.stringify(change.currentValue); const from = JSON.stringify(change.previousValue); const changeLog = `${propName}: changed from ${from} to ${to} `; this.changelog.push(changeLog); } }
That is all there is to it.
message: changed from undefined to "" customer: changed from undefined to {} message: changed from "" to "H" message: changed from "H" to "He" message: changed from "He" to "Hel" message: changed from "Hel" to "Hell" message: changed from "Hell" to "Hello"
OnChanges does not fire always
- Change the customer code and name, then press the UpdateCustomer button.
- The client name is displayed in the Child Components, but the OnChanges event is not fired.
- This is deliberate behavior.
Template is Updated
Why onChanges does not fire?
- Create a new customer and copy the previous client's information to the new one.
- Using the ngDoCheck lifecycle hook, we can perform our own change detection.
updateCustomer() { this.customer= new Customer(); //Add this this.customer.name = this.name; this.customer.code = this.code; }