Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Fraga and Clark Valberg
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Fraga Clark Valberg

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

By
Published in

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel