ngOnChanges In Angular

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>`

Angular raises the OnChanges hook event in the child component whenever the parent changes the value of the message property, allowing it to react.

How does it work

The ngOnChanges() method receives an object that maps each modified property name to a SimpleChange object that contains the current and previous property values. It is possible to iterate through the altered properties and take action.

SimpleChange

SimpleChange is a straightforward class with only three properties.

Property NameDescription
previousValue:anyPrevious value of the input property.
currentValue:anyNew or current value of the input property.
FirstChange():booleanBoolean value, which tells us whether it was the first time the change has taken place

SimpleChanges

Our component's @Input properties are all given a SimpleChange object (if Property is changed)

SimpleChanges is the object that contains all of the SimpleChange objects' instances. The name of the @Input property can be used as the key to access those SimpleChange objects.

If the two input attributes message1 and message2 are modified, for example, the SimpleChanges object will look like this.
{
  "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

Create a customer.ts class in the src/app folder.
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;
  }
 
}

Let's take a peek at the source code.

For the message, code, and name, we have three user input areas. The Customer object is updated when the UpdateCustomer button is pressed.
<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);
        }
    }
}

Let's take a closer look at each line of code.

First, we import the Angular Core components Input, OnInit, OnChanges, SimpleChanges, and SimpleChange.
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.

Now we can use our OnChanges hook.

When you run the code and type Hello, you should see the following log.
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"

You should see the changes object when you open the developer console.

It's worth noting that the OnChanges hook was called before the OnInit hook.

When ngOnInit() is called, the initial values bound to inputs are available.

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

Angular change detection mechanism includes updating the DOM. The change detector looks for changes in all bound properties and updates the DOM if any are found.

We have two bound properties in the child component template. message and customer.name are both required fields. As a result, the change detector just examines these two attributes before updating the DOM. The code property is likewise present in the customer object. It will never be checked by the change detector.

Why onChanges does not fire?

The OnChanges hook is also raised by the Change detector. However, it compares using a different method.

For detecting changes to the input properties, the change detector employs the rigorous equality operator ===. The comparison above works well for primitive data types like strings.

However, this fails in the case of an object like a customer. The tight testing of Arrays/Objects means that just the references are verified. Angular does not trigger the OnChanges hook since the reference to the customer remains the same.

That leaves us with two options.
  1. Create a new customer and copy the previous client's information to the new one.
  2. Using the ngDoCheck lifecycle hook, we can perform our own change detection.
Every time you call the updateCustomer method, a new instance of the customer is created.
updateCustomer() {
  this.customer= new Customer();    //Add this
  this.customer.name = this.name;
  this.customer.code = this.code;
}

When you run the code, you'll notice that the onChanges event is dispatched whenever a customer is updated.

The second option is to use the ngDoCheck lifecycle hook, which will be covered in the next article.

Conclusion

We learned how to use the ngOnChanges method in this lesson. We also learned how to use the SimpleChanges object to determine which attributes have been altered.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post