Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created December 5, 2018 11:38
Show Gist options
  • Save bennadel/52972a2c01b79efba2a1b89a2e179c99 to your computer and use it in GitHub Desktop.
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
// 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()
}
];
}
}
// 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." ) );
}
}
}
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