Using NgOnChanges Collection With Dot-Notation And TypeScript In Angular 2.4.4
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.
Want to use code from this post? Check out the license.
Reader Comments
This might get better in the next release of typescript (2.2). See:
https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#dotted-property-for-types-with-string-index-signatures
Nice article,informative
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.
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.
I'm excited to see this being "fixed" in TypeScript 2.2. Woot and double-woot!