Created
December 5, 2018 11:38
-
-
Save bennadel/52972a2c01b79efba2a1b89a2e179c99 to your computer and use it in GitHub Desktop.
Using The "Definite Assignment Assertion" To Define Required Input Bindings In Angular 7.1.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { Component } from "@angular/core"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
interface User { | |
id: number; | |
name: string; | |
email: string; | |
avatarUrl: string; | |
startedAt: number; | |
} | |
@Component({ | |
selector: "my-app", | |
styleUrls: [ "./app.component.less" ], | |
template: | |
` | |
<div *ngFor="let user of users"> | |
<bn-badge [user]="user"></bn-badge> | |
</div> | |
<!-- | |
We know this one will break because there is no [user] binding. This is just | |
here to demonstrate what it looks like when it breaks. | |
--> | |
<div> | |
<bn-badge></bn-badge> | |
</div> | |
` | |
}) | |
export class AppComponent { | |
public users: User[]; | |
// I initialize the app component. | |
constructor() { | |
this.users = [ | |
{ | |
id: 1, | |
name: "Kim Doro", | |
email: "ben+kim@bennadel.com", | |
avatarUrl: "http://www.gravatar.com/avatar/5cbcec91c352ed84fa4ad6fc42fd2a05.jpg?s=150", | |
startedAt: Date.now() | |
}, | |
{ | |
id: 2, | |
name: "Sarah O'Neill", | |
email: "ben+sarah@bennadel.com", | |
avatarUrl: "http://www.gravatar.com/avatar/a65ac17d587bc4b2a0d4075fc8cb2938.jpg?s=150", | |
startedAt: Date.now() | |
}, | |
{ | |
id: 3, | |
name: "Tricia Nakatomi", | |
email: "ben+tricia@bennadel.com", | |
avatarUrl: "http://www.gravatar.com/avatar/e75d5660d83e33924a51b22cc1db0a91.jpg?s=150", | |
startedAt: Date.now() | |
} | |
]; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { ChangeDetectionStrategy } from "@angular/core"; | |
import { Component } from "@angular/core"; | |
import { OnChanges } from "@angular/core"; | |
import { OnInit } from "@angular/core"; | |
import { SimpleChanges } from "@angular/core"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
interface User { | |
name: string; | |
email: string; | |
avatarUrl: string; | |
} | |
@Component({ | |
selector: "bn-badge", | |
inputs: [ "user" ], | |
changeDetection: ChangeDetectionStrategy.OnPush, | |
styleUrls: [ "./badge.component.less" ], | |
template: | |
` | |
<div class="layout"> | |
<div class="layout__avatar"> | |
<img [src]="user.avatarUrl" class="avatar" /> | |
</div> | |
<div class="layout__info info"> | |
<div class="info__name"> | |
{{ user.name }} | |
</div> | |
<div class="info__email"> | |
{{ user.email }} | |
</div> | |
</div> | |
</div> | |
` | |
}) | |
export class BadgeComponent implements OnInit, OnChanges { | |
// Since the user is provided by an outside context (as a required input binding), | |
// it's defined value will not be know at instantiation time. As such, we'll need | |
// to allow it to be null (and then enforce its value later on). | |
public user: User | null; | |
// I initialize the badge component. | |
constructor() { | |
this.user = null; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I get called after input bindings have been changed. | |
// -- | |
// CAUTION: If the calling context provides no input bindings on the bn-badge tag, | |
// this method will never get called. | |
public ngOnChanges( changes: SimpleChanges ) : void { | |
this.assertUser(); | |
} | |
// I get called after the input bindings have been defined for the first time. | |
// -- | |
// NOTE: This method still gets called even if there are no input bindings. This is | |
// different from the ngOnChanges() method above, which will only get called if input | |
// bindings are actually defined. | |
public ngOnInit() : void { | |
this.assertUser(); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I assert that the user is defined (ie, that the required input binding was | |
// actually provided by the calling context). | |
private assertUser() : void { | |
if ( ! this.user ) { | |
throw( new Error( "Required input [user] not provided." ) ); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class BadgeComponent implements OnInit, OnChanges { | |
// Since the user is provided by an outside context (as a required input binding), | |
// it's defined value will not be know at instantiation time. As such, we'll use the | |
// "Definite Assignment Assertion" (!) to tell TypeScript that we know this value | |
// will be defined in some way by the time we use it; and, that TypeScript should | |
// not worry about the value until then. | |
public user!: User; | |
// I initialize the badge component. | |
constructor() { | |
// We've used the "Definite Assignment Assertion" to tell TypeScript that this | |
// value will be provided by a non-obvious means (provided after instantiation). | |
// As such, we don't need to initialize it. | |
// -- | |
// this.user = null; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I get called after input bindings have been changed. | |
// -- | |
// CAUTION: If the calling context provides no input bindings on the bn-badge tag, | |
// this method will never get called. | |
public ngOnChanges( changes: SimpleChanges ) : void { | |
this.assertUser(); | |
} | |
// I get called after the input bindings have been defined for the first time. | |
// -- | |
// NOTE: This method still gets called even if there are no input bindings. This is | |
// different from the ngOnChanges() method above, which will only get called if input | |
// bindings are actually defined. | |
public ngOnInit() : void { | |
this.assertUser(); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I assert that the user is defined (ie, that the required input binding was | |
// actually provided by the calling context). | |
private assertUser() : void { | |
if ( ! this.user ) { | |
throw( new Error( "Required input [user] not provided." ) ); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment