Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Luis Majano
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Luis Majano

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

By
Published in

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel