Type-checking templates in Angular ViewEngine and Ivy

Beware what enabling full template type-checking in Ivy will bring us.

Original 📷 by Jesse Bowser

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

In this article, I will dive into Angular type-checking system, show how it’s done in ViewEngine and also highlight the latest features and improvements which are available in Ivy.

After reading this article you will:

learn how Angular type-checks templates

see the difference between ViewEngine (VE) and Ivy type-checking

be familiar with new Ivy type-checking features

be more confident with template errors since you will know how to see generated type-checking code

Let’s get started.

A little bit of history.

I don’t remember from which Angular version we started getting strange errors in our production build but you should be familiar with some of them:

Property ‘resisterUser’ does not exist on type ‘LoginRegisterComponent’ Expected 0 arguments, but got 1 Supplied parameters do not match any signature of call target

It shows us that Angular treats a component’s template as a partial TypeScript file.

But how?

Type Check Block to the rescue

The answer is simple.

Angular emits all binding expressions in a way that can be type-checked. To be more precise, Angular creates Type Check Blocks(TCBs) based on the component template.

Basically, a Type Check Block is a block of TypeScript

code which can be inlined into source files, and when type checked by the TypeScript compiler will give us information about any typing errors in template expressions.

Let’s suppose that we have a simple component:

Here’s how the type check block looks like for this component:

As you can guess, the code above will fail to compile at this point _decl0_1.foo since the foo property does not exist on AppComponent class.

This is just a concept of how it works. Let’s look how it’s handled by ViewEngine and Ivy.

Type-checking in View Engine

You should be familiar that Angular compiler generates factory files for all components and modules.

In addition, each NgModule factory file also contains generated type-check blocks of all components declared in this module. In other words, all module-based factories will contain generated TCBs.

A simple module like:

will produce synthetic file like:

So what’s is currently being checked with fullTemplateTypeCheck enabled?

# component member access

In case we forgot to declare some property or method in component Angular’s type-checking system will produce a diagnostic:

Produced TCB looks like:

# event bindings

The compiler will remind us that we forgot to add arguments to the method executed from template

Note that I intentionally left the test method without parameters. If we try to build this component in AOT mode we’ll get the error Expected 0 arguments, but got 1 .

# HostListener

Consider the following code in your component:

The compiler will generate TCB as follows:

So we again get the similar to the error we got in event binding above:

Directive AppComponent, Expected 0 arguments, but got 1.

# compiler can understand almost all template expressions like:

# type-checking for pipe

The types of the pipe’s value and arguments are matched against the transform() call.

# type-safety for directives accessed by template reference variable ‘#’

# $any keyword

We can disable type-checking of a binding expression by surrounding the expression in a call to the $any()

$any(this).missing // ok

So that there shouldn’t be any problem in case we don’t have missing property defined.

# non-null type assertion operator

It’s helpful when we use “strictNullChecks”: true in tsconfig.json

{{ obj!.prop }}

# type guard for ngIf

Let’s say we have added strictNullChecks option in tsconfig.json file and our component contains the following property:

person?: Person;

We can write a template like:

<div *ngIf="person">{{person.name}}</div>

This feature makes it possible to guard person.name access by two different ways:

ngIfTypeGuard wrapper

If we add the following static property to the ngIf directive:

static ngIfTypeGuard: <T>(v: T|null|undefined|false) => v is T;

then the compiler will generate TCB similar to:

if (NgIf.ngIfTypeGuard(instance.person)) {

instance.person.name

}

The ngIfTypeGuard guard guarantees that instance.person used in the binding expression will never be undefined .

2. Use expression as a guard

By adding the following static property to the ngIf directive:

public static ngIfUseIfTypeGuard: void;

we add more accurate type-checking by allowing a directive to use the expression passed directly to a property as a guard instead of filtering the type through a type expression.

if (instance.person) {

instance.person.name

}

You can read more on this in Angular docs https://angular.io/guide/aot-compiler#type-narrowing