Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Elliott Sprehn
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Elliott Sprehn@ElliottZ )

Using NgOnChanges Collection With Dot-Notation And TypeScript In Angular 2.4.4

By Ben Nadel on

A couple of months ago, I looked at using dot-notation vs. array or bracket notation with TypeScript objects that have generic key-value interfaces. In that post, I demonstrated that you needed to use array-notation or the TypeScript transpiler will complain. One of the places in which this has continued to be a nuisance is the ngOnChanges() life-cycle method in Angular 2 components. Lately, however, I have started sub-classing the SimpleChanges interface in order to allow for the more natural dot-notation.

If you look at the SimpleChanges interface in the Angular 2 source code, you can see that it uses a generic key-value map:

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

This generic interface forces us to use array-notation / bracket-notation in our ngOnChanges() life-cycle method. So, for example, if I had an Angular 2 component that accepted an Input binding, the use of dot-notation when inspecting the ngOnChanges() argument would throw an error:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  • import { OnChanges } from "@angular/core";
  • import { SimpleChanges } from "@angular/core";
  •  
  • @Component({
  • moduleId: module.id,
  • selector: "my-widget",
  • inputs: [ "value" ],
  • template:
  • `
  • <strong>Value</strong>: {{ value }}
  • `
  • })
  • export class MyWidgetComponent implements OnChanges {
  •  
  • public value: string;
  •  
  • // I initialize the emoticon button component.
  • constructor() {
  •  
  • this.value = "";
  •  
  • }
  •  
  • // I get called whenever the bound inputs change (including the first binding).
  • public ngOnChanges( changes: SimpleChanges ) : void {
  •  
  • if ( changes.value && ! changes.value.isFirstChange() ) {
  •  
  • console.log(
  • "Value change from",
  • changes.value.previousValue,
  • "to",
  • changes.value.currentValue
  • );
  •  
  • }
  •  
  • }
  •  
  • }

Here you can see that I am declaring the ngOnChanges() argument as type SimpleChanges. And, when I try to use dot-notation to inspect the "changes.value" property, TypeScript logs the following error:

Property 'value' does not exist on type 'SimpleChanges'. (TS2339)

To get around this problem, I've started to sub-class the SimpleChanges interface within the component, explicitly defining the input properties that I expect to consume:

  • interface InputChanges extends SimpleChanges {
  • value?: SimpleChange;
  • }

This creates a new interface that extends the generic key-value mapping of the SimpleChanges interface, but also provides for an optional and explicit ".value" property. Now, in my Angular 2 component, I can use this interace when defining my ngOnChanges() life-cycle method:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  • import { OnChanges } from "@angular/core";
  • import { SimpleChange } from "@angular/core";
  • import { SimpleChanges } from "@angular/core";
  •  
  • interface InputChanges extends SimpleChanges {
  • value?: SimpleChange;
  • }
  •  
  • @Component({
  • moduleId: module.id,
  • selector: "my-widget",
  • inputs: [ "value" ],
  • template:
  • `
  • <strong>Value</strong>: {{ value }}
  • `
  • })
  • export class MyWidgetComponent implements OnChanges {
  •  
  • public value: string;
  •  
  • // I initialize the emoticon button component.
  • constructor() {
  •  
  • this.value = "";
  •  
  • }
  •  
  • // I get called whenever the bound inputs change (including the first binding).
  • public ngOnChanges( changes: InputChanges ) : void {
  •  
  • if ( changes.value && ! changes.value.isFirstChange() ) {
  •  
  • console.log(
  • "Value change from",
  • changes.value.previousValue,
  • "to",
  • changes.value.currentValue
  • );
  •  
  • }
  •  
  • }
  •  
  • }

This time, when I run the Angular 2 application, everything works perfectly; TypeScript doesn't complain about my use of "changes.value" because the newly sub-classed interface defines an explicit ".value" property.

TypeScript definitely takes some getting used to. But, the more I use it, the more I like it. It really forces me to think about what data I can count on; and, what data may exist, but is unsafe to reference. Sub-classing the SimpleChanges interface adds explicitness to my Angular 2 components and allows me to use dot-notation without error.



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

The site contains a very great blog. the information present in this site will be very useful for us. thank you for sharing the blog with us.

Reply to this Comment

A really good idea. Thanks for this post!

For my part, I love TypeScript even more than JavaScript. Quality and readability is so much higher with typings.

Reply to this Comment

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.