Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at The Plaza Hotel 2012 (New York City) with: Judson Terrell
Ben Nadel at The Plaza Hotel 2012 (New York City) with: Judson Terrell

ngOnChanges() Life Cycle Hook Only Gets Invoked If Calling Context Actually Provides Input Bindings In Angular 7.1.1

By Ben Nadel on

The other day, when experimenting with the "Definite Assignment Assertion" in TypeScript, I stumbled upon the fact that my mental model for the ngOnChanges() life cycle hook in Angular was inaccurate. I had previously assumed that if an Angular Directive declared input bindings (as part of the Directive or Component meta-data), then the ngOnChanges() life cycle method would be called at least once. However, what I now realize is that if the calling context doesn't provide any input bindings, the ngOnChanges() method is never invoked, regardless of what's in the Directive meta-data.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

To see this fact in action, let's create a simple Widget component that accepts a [value] input binding and logs the invocation of the OnChanges() and OnInit() interface methods:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  • import { OnChanges } from "@angular/core";
  • import { OnInit } from "@angular/core";
  • import { SimpleChanges } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-widget",
  • inputs: [ "value" ],
  • styleUrls: [ "./widget.component.less" ],
  • template:
  • `
  • I am a widget <strong *ngIf="value">with a value</strong>
  • `
  • })
  • export class WidgetComponent implements OnInit, OnChanges {
  •  
  • // This is the INPUT property.
  • public value: any = null;
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  • // I get called after input bindings have been changed.
  • // --
  • // CAUTION: If the calling context DOES NOT PROVIDE ANY INPUT BINDINGS, then this
  • // event-handler will not be called (even if the component meta-data states that this
  • // component has input properties).
  • public ngOnChanges( changes: SimpleChanges ) : void {
  •  
  • console.log( "Widget ngOnChanges event-handler." );
  •  
  • }
  •  
  •  
  • // I get called after the input bindings have been checked for the first time.
  • public ngOnInit() : void {
  •  
  • console.log( "Widget ngOnInit event-handler." );
  •  
  • }
  •  
  • }

As you can see, the Component meta-data defines one "input binding" that maps to the property, "value".

Now, let's create an AppComponent that uses two instances of this WidgetComponent - one with an input binding and one without:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-app",
  • styleUrls: [ "./app.component.less" ],
  • template:
  • `
  • <!-- Providing input binding. -->
  • <my-widget [value]=" 'meep meep' "></my-widget>
  •  
  • <!-- OMITTING input binding. -->
  • <my-widget></my-widget>
  • `
  • })
  • export class AppComponent {
  • // ...
  • }

As you can see, only the first "my-widget" element is provided with a [value] input binding. The second my-widget element gets nothing. And, when we run this code in the browser, we get the following console output:


 
 
 

 
 ngOnChanges() life cycle method is not invoked if calling context omits input bindings in Angular 7.1.1. 
 
 
 

As you can see, if the calling context provides a [value] input binding, the ngOnChanges() life cycle method is invoked. However, if the calling context completely omits the [value] input binding, the ngOnChanges() method is skipped, calling only the ngOnInit() method. In other words, the invocation of the ngOnChanges() life cycle method is controlled by the calling context, not the Directive meta-data.

The reason that I'm underscoring this point (for myself) is that I've historically been doing all my Directive input validation in the ngOnChanges() life cycle method. What I know now is that this is not sufficient. While the ngOnChanges() life cycle method can help validate invalid inputs in Angular, it can't validate "no inputs". For that case, I have to use the ngOnInit() life cycle method.

I am sure I am the only developer who was confused on this point. But, on the off-chance that I'm not alone, hopefully this will help other Angular developers.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.