Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Using $any() To Temporarily Disable Type-Checking Within A Component Template In Angular 9.0.0-rc.4

By Ben Nadel on

In recent releases of Angular, type-checking has been extended to include our component templates. Which is awesome for catching compile-time bugs. However, the type-checking within our template is not quite as clever as the type-checking within our TypeScript code. For example, type-checking doesn't appear to work well with discriminated unions. However, I just learned about a template directive call $any(), which allows us to opt-out of type-checking within a single template binding in our Angular components. Since this was news to me, I wanted to pass this on in case it was news to anyone else.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

To see how this works - and when we might need it - let's take a look at a small Angular component that has a single property - selection - that could be one of several different types: null, "bff", "random", and Person. Since three of these types are "static", so to speak, we can check for them explicitly; and, if none of them match, we can assume that the default is of type Person:

// Import the core angular services.
import { Component } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

interface Person {
	id: string;
	name: string;
}

@Component({
	selector: "app-root",
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<p>
			<a (click)="setRandomSelection()">
				Set random selection
			</a>
		</p>

		<ng-container [ngSwitch]="selection">
			<ng-template [ngSwitchCase]="null">
				Null
			</ng-template>
			<ng-template [ngSwitchCase]="( 'bff' )">
				BFF
			</ng-template>
			<ng-template [ngSwitchCase]="( 'random' )">
				Random
			</ng-template>
			<ng-template ngSwitchDefault>
				{{ selection.name }}
			</ng-template>
		</ng-container>
	`
})
export class AppComponent {

	public selection: Person | "bff" | "random" | null;

	// I initialize the app component.
	constructor() {

		this.selection = this.getRandomSelection();

		switch ( this.selection ) {
			case null:
				console.log( "Selection is null" );
			break;
			case "random":
				console.log( "Selection is Random" );
			break;
			case "bff":
				console.log( "Selection is BFF" );
			break;
			// NOTE: In the code, the TypeScript compiler is smart enough to know that
			// the Default case must be a Person since the other potential types have
			// already been ruled-out using the above Case values.
			default:
				console.log( "Selection person:", this.selection.name );
			break;
		}

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I return a random selection.
	public getRandomSelection() : Person | "bff" | "random" | null {

		switch ( Math.floor( Math.random() * 4 ) ) {
			case 0:
				return( null );
			break;
			case 1:
				return( "random" );
			break;
			case 2:
				return( "bff" );
			break;
			default:
				return({
					id: "1",
					name: "Kim"
				});
			break;
		}

	}


	// I update the selection randomly.
	public setRandomSelection() : void {

		this.selection = this.getRandomSelection();

	}

}

As you can see, we are using two switch statements: one in the TypeScript code of our Component and one in the HTML template. The code one compiles quite nicely; but, the compiler throws an error for the template-based ngSwitchDefault markup:

Angular cannot deduce type in ngSwitch statement in template.

As you can see, despite the fact that we have ruled-out the simple types, the Angular AoT (Ahead of Time) compiler cannot deduce the fact that the ngSwitchDefault must be referring to a value of type Person. To get around this without much ceremony, we can simply wrap the variable in an $any() call:

<ng-container [ngSwitch]="selection">
	<ng-template [ngSwitchCase]="null">
		Null
	</ng-template>
	<ng-template [ngSwitchCase]="( 'bff' )">
		BFF
	</ng-template>
	<ng-template [ngSwitchCase]="( 'random' )">
		Random
	</ng-template>
	<ng-template ngSwitchDefault>

		<!--
			The Angular Ahead-of-Time (AoT) compiler cannot figure out that
			"selection" must be a Person at this point. As such, we are going to
			temporarily disable type-checking using the $any() pseudo-function.
		-->
		{{ $any( selection ).name }}

	</ng-template>
</ng-container>

As you can see, we've changed selection.name to $any(selection).name within our HTML template. The $any() is akin to casting to the any type within TypeScript, and tells Angular to skip any type-checking on the wrapped value. And with this change, the Angular code compiles just fine:

The AoT compiler will bypass template type-checking if a binding is wrapping in $any() in Angular 9.0.0-rc.4.

Now, obviously, we don't get all the goodness and safety of type-checking when we tell Angular to explicitly bypass the type-checker. However, in edge-cases, this works quite nicely without making the component template more complicated.



Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
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.