Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Exploring Angular Lifecycle Hooks - OnChanges

Welcome back to our blog series, Exploring Angular Lifecycle Hooks!

Available Lifecycle Hooks covered in this series:

Let’s continue the series with one of the under-utilized, yet extremely helpful hooks, ngOnChanges.

According to the Angular Docs, OnChanges is used to “Respond when Angular (re)sets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. Called before ngOnInit() and whenever one or more data-bound input properties change.”

In plain English, this lifecycle hook will allow us to monitor the value of Inputs to our components and directives and allow us to branch our logic to react differently when those values do change.

In this article, we will review how to implement OnChanges, a common use case for OnChanges, and a potential alternative using setters.

Angular ngOnChanges

OnChanges is an Angular lifecycle method, that can be hooked into components and directives in Angular. By defining a specific method named ngOnChanges on our class, we are letting the Angular runtime know that it should call our method at the appropriate time. This allows us to implement logic in our classes to handle updates to our changing Input data.

Implementing OnChanges

In order to implement OnChanges, we will follow two simple steps.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Add OnChanges after the implements keyword

The first step to implementing OnChanges is to add OnChanges after the implements keyword on a component or directive.

Here’s a common component that lacks lifecycle hooks:

import { Component } from '@angular/core';

@Component({...})
export class SomeCoolComponent {}

Let’s import OnChanges from Angular’s core package. Once imported we can create a contract with implements OnChanges:

import { Component, OnChanges } from '@angular/core';

@Component({...})
export class SomeCoolComponent implements OnChanges {}

Fun Fact Time: Technically it’s not required to implement the interface, Angular will call ngOnChanges regardless, however, it’s very helpful for type-checking, and to allow other developers to quickly identify which lifecycle hooks are in use on this class.

Add the ngOnChanges method to our class

With our newly added OnChanges after implements TypeScript IntelliSense will underline the class declaration in red, giving a warning that ngOnChanges was not found. We can resolve that issue by creating our ngOnChanges method.

Example Component Before:

import { Component, OnChanges } from '@angular/core';

@Component({...})
export class SomeCoolComponent implements OnChanges {}

Example Component After:

import { Component, OnChanges } from '@angular/core';

@Component({...})
export class SomeCoolComponent implements OnChanges {
  ngOnChanges(changes: SimpleChanges) {
    // Input change handling logic goes here   
  }
}

The SimpleChanges Object

As you can see above, the ngOnChanges method takes in a changes: SimpleChanges parameter. SimpleChanges is an object that will have a property for each Input defined in your component or directive.

Here is the shape of the SimpleChanges object:

interface SimpleChanges {
  [propName: string]: SimpleChange;
}

Each property defined in SimpleChanges will have a child SimpleChange object:

interface SimpleChange {
  currentValue: any;
  previousValue: any;
  firstChange: boolean;
  isFirstChange(): boolean;
}

As you can see, the SimpleChange object can be really helpful. It allows us to inspect the changes flowing through ngOnChanges and make intelligent decisions in our logic based on the values in this object.

OnChanges In The Real World

Implementing OnChanges was a simple two-step process. Let’s dive in and review a real-world use case for OnChanges. At the beginning of the article, we mentioned that Angular recommends the following: “Respond when Angular (re)sets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. Called before ngOnInit() and whenever one or more data-bound input properties change.”

Revisiting The Github Repository Explorer Example

Let’s revisit an example from my previous OnInit article in this series, the Github Repository Explorer.

If we remember correctly, we had a component named GithubReposComponent, that had an Input for the repoLimit. In the example, we initialized our repos$ with a call to the GithubService.getMostStarredRepos and passed in the repoLimit.

Here’s the full component:

// github-repos.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs';

import { GithubService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of (repos$ | async)"
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposComponent implements OnInit {
  @Input() repoLimit: number;

  repos$: Observable<GithubRepo[]>;

  constructor(private githubService: GithubService) {}

  ngOnInit() {
    this.repos$ = this.githubService.getMostStarredRepos(this.repoLimit);
  }
}

OnChanges, the hero we all need

If we are handling the repoLimit Input in the ngOnInit, we might say to ourselves: “what’s the problem?” Well, the problem is that we only handle repoLimit in the ngOnInit. This means that if we were to have a new value flow down from the parent in the repoLimit Input our repos$ would not re-retrieve the new set of repos with the new limit.

How do we fix our component so that our repos$ are re-retrieved every time repoLimit changes? Well, this is where our new hero, OnChanges comes to the rescue.

Let’s implement OnChanges and add our new ngOnChanges(changes: SimpleChanges) method to our component. Inside that new method, let’s check for changes.repoLimit to be truthy and if so, then let’s initialize our repos$ observable to a service call passing in the changes.repoLimit.currentValue to retrieve the latest value for the repoLimit Input.

// github-repos.component.ts

import { Component, OnChanges, Input } from '@angular/core';
import { Observable } from 'rxjs';

import { GithubService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of (repos$ | async)"
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposComponent implements OnChanges {
  @Input() repoLimit: number;

  repos$: Observable<GithubRepo[]>;

  constructor(private githubService: GithubService) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes.repoLimit) {
      this.repos$ = this.githubService.getMostStarredRepos(changes.repoLimit.currentValue);
    }    
  }
}

Fantastic! Now our component will re-retrieve our repos$ every time repoLimit changes.

Setters vs ngOnChanges

Reviewing the previous example, let’s refactor our component a bit more and make use of an alternative to OnChanges that will also allow us to re-retrieve our repos$ each time repoLimit changes. To do so, we will convert the repoLimit Input into a TypeScript setter using the set syntax.

Creating a refreshRepos Method

First, let’s create a new method named refreshRepos(limit: number) and move the repos$ initialization into that new method. Our new refreshRepos method should look like this:

refreshRepos(limit: number) {
  this.repos$ = this.githubService.getMostStarredRepos(limit);
}

Removing the OnChanges Implementation

Next, let’s remove the OnChanges implementation from our component, first removing the implements OnChanges and then removing the ngOnChanges method altogether.

Our class declaration will look something like this with OnChanges and ngOnChanges removed:

export class GithubReposComponent {...}

Converting the repoLimit Input to a setter

TypeScript setters provide a way to define a method that is called every time the value on the class is set or changed.

Now, let’s add a setter to our Input() repoLimit: number. In the set for repoLimit we will make a call to our refreshRepos method passing in the newLimit.

Our repoLimit setter will look like this:

@Input() set repoLimit(newLimit: number) {
  this.refreshRepos(newLimit);
}

A Refactored Component

Congratulations! We have completed refactoring our component to use a setter instead of OnChanges. This provides a simpler solution to our problem.

The finished component will look like this:

// github-repos.component.ts

import { Component, Input } from '@angular/core';
import { Observable } from 'rxjs';

import { GithubService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of (repos$ | async)"
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposComponent {
  @Input() set repoLimit(newLimit: number) {
    this.refreshRepos(newLimit);
  }

  repos$: Observable<GithubRepo[]>;

  constructor(private githubService: GithubService) {}

  refreshRepos(limit: number) {
    this.repos$ = this.githubService.getMostStarredRepos(limit);
  }
}

As we review the above example, we might ask ourselves, does this still work on initialization? Well, the answer is yes! This is because the repoLimit setter is called when the Input is first set, and then every time thereafter it is changed.

Conclusion

Well, folks, we have reached the end of another article in this series on Angular Lifecycle hooks! If you take anything away from this article, I hope it is that OnChanges is powerful, but should be used wisely. And maybe, just maybe, you should consider using TypeScript setters instead.

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover