Type-checking templates in Angular ViewEngine and Ivy
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.

Type-checking templates in Angular ViewEngine and Ivy
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.


Beware what enabling full template type-checking in Ivy will bring us.
AngularInDepth is moving away from Medium. This article, its updates and more recent articles are hosted on the new platform inDepth.dev
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.Link to this section
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 rescueLink to this section
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:
<>Copy@Component({ selector: 'app-root', template: '{{ foo }}' }) export class AppComponent {}
Here’s how the type check block looks like for this component:
<>Copyvar _decl0_1: AppComponent = (<any>(null as any)); function _View_AppComponent_1_0(): void { const currVal_0: any = _decl0_1.foo; }
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 EngineLink to this section
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:
<>Copy@NgModule({ ... declarations: [ AppComponent ] }) export class AppModule {}
will produce synthetic file like:
<>Copyimport * as i0 from '@angular/core'; import * as i1 from './app.module'; import * as i2 from '@angular/common'; import * as i3 from './foo.component'; import * as i4 from './app.component'; import * as i5 from '@angular/platform-browser'; import * as i6 from './foo.module'; export const AppModuleNgFactory:i0.NgModuleFactory<i1.AppModule> = (null as any); var _decl0_0:i2.NgClass = (<any>(null as any)); var _decl0_1:i2.NgComponentOutlet = (<any>(null as any)); var _decl0_2:i2.NgForOf<any> = (<any>(null as any)); var _decl0_3:i2.NgIf = (<any>(null as any)); // ... var _decl0_28:i0.TemplateRef<any> = (<any>(null as any)); var _decl0_29:i0.ElementRef<any> = (<any>(null as any)); function _View_AppComponent_Host_1_0():void { var _any:any = (null as any); } function _View_AppComponent_1_0():void { var _any:any = (null as any); const currVal_0:any = _decl0_12.title; currVal_0; }
So what’s is currently being checked with fullTemplateTypeCheck
enabled?
# component member accessLink to this section
In case we forgot to declare some property or method in component Angular’s type-checking system will produce a diagnostic:
<>Copy{{ unknownProp }}
Produced TCB looks like:
<>Copyvar _decl0_12:i4.AppComponent = (<any>(null as any)); function _View_AppComponent_1_0():void { var _any:any = (null as any); const currVal_0:any = _decl0_12.unknownProp; // Property 'unknownProp' does not exist on type 'AppComponent'. currVal_0; }
# event bindingsLink to this section
The compiler will remind us that we forgot to add arguments to the method executed from template
<>Copy@Component({ selector: 'app-root', template: '<button (click)="test($event)">Test</button>' }) export class AppComponent { test() {} }
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
.
<>Copyvar _decl0_1: AppComponent = (<any>(null as any)); function _View_AppComponent_1_0(): void { var _any:any = (null as any); const pd_1:any = ((<any>_decl0_1.test(_any)) !== false); ^^^^^^^^^^ }
# HostListener Link to this section
Consider the following code in your component:
<>Copy@HostListener('click', ['$event']) onClick() {}
The compiler will generate TCB as follows:
<>Copyfunction _View_AppComponent_Host_1_0():void { var _any:any = (null as any); const pd_0:any = ((<any>_decl0_12.onClick(_any)) !== false); }
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:Link to this section
<>Copy{{ getSomething() }} {{ obj[prop][subProp] }} {{ someMethod({foo: 1, bar: '2'}) }}
# type-checking for pipeLink to this section
The types of the pipe’s value and arguments are matched against the transform()
call.
<>Copy<div>{{"hello" | aPipe}}</div> // Argument of type "hello" is not assignable to parameter of type number {{ ('Test' | lowercase).startWith('test') }} // error TS2551: Property 'startWith' does not exist on type 'string'. Did you mean 'startsWith'?
# type-safety for directives accessed by template reference variable ‘#’Link to this section
<>Copy<div aDir #aDir="aDir">{{aDir.fname}}</div> Property 'fname' does not exist on type 'ADirective'. Did you mean 'name'?
# $any keyword
Link to this section
We can disable type-checking of a binding expression by surrounding the expression in a call to the $any()
<>Copy$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 operatorLink to this section
It’s helpful when we use “strictNullChecks”: true
in tsconfig.json
<>Copy{{ obj!.prop }}
# type guard for ngIfLink to this section
Let’s say we have added strictNullChecks
option in tsconfig.json
file and our component contains the following property:
<>Copyperson?: Person;
We can write a template like:
<>Copy<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:
<>Copystatic ngIfTypeGuard: <T>(v: T|null|undefined|false) => v is T;
then the compiler will generate TCB similar to:
<>Copyif (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:
<>Copypublic 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.
<>Copyif (instance.person) { instance.person.name }
You can read more on this in Angular docs https://angular.io/guide/aot-compiler#type-narrowing
Ivy type-checkingLink to this section
Remember, in ViewEngine TCBs are placed in NgModule factories. TypeScript has to re-parse and re-type-check those files when processing the type-checking program.
The new Ivy compiler uses a far more performant approach. It augments the program with a single synthetic __ng_typecheck__.ts
file, into which all TCBs are generated.
Additionally, Ivy compiler introduced special kind of methods called type constructors.
A type constructor is a specially shaped TypeScript method that permits type inference of any generic type parameters of the class from the types of expressions bound to inputs or outputs, and the types of elements that match queries performed by the directive. It also catches any errors in the types of these expressions.
The type constructor is never called at runtime, but is used in type-check blocks to construct directive types.
A type constructor for NgFor
directive looks like:
<>Copystatic ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, ‘ngForOf’|’ngForTrackBy’|’ngForTemplate’>>): NgForOf<T>;
A typical usage would be:
<>CopyNgForOf.ngTypeCtor(init: {ngForOf: [‘foo’, ‘bar’]}); // Infers a type of NgForOf<string>.
Type constructors are also inlined into __ng_typecheck__.ts
file.
There are some exceptions when Ivy has to inline TCB blocks into the current processing file:
- The class component doesn’t have the
export
modifier - The component class has constrained generic types, i.e.
<>Copyclass Comp<T extends { name: string }> {}
But in most cases, you will find all TCBs in __ng_typecheck__.ts
file.
Let’s see which improvements in type-checking have been made in Ivy.
# Type checking of directive inputsLink to this section
It’s now possible to get an error if you’ve passed a property of a wrong type to a directive:
<>Copy<app-child [prop]="'text'"></app-child> export class ChildComponent implements OnInit { @Input() prop: number;
In VE the generated code looks like:
<>Copyfunction _View_AppComponent_1_0():void { var _any:any = (null as any); const currVal_0:any = 'text'; currVal_0; }
Ivy brings us improved TCB:
<>Copyconst _ctor1: (init: Partial<Pick<i1.ChildComponent, "prop">>) => i1.ChildComponent = (null!); function _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = document.createElement("app-child"); var _t2 = _ctor1({ prop: "text" }); // error TS2322: Type 'string' is not assignable to type 'number'. } }
Case with an unobvious directive:
<>Copy<input ngModel [maxlength]="max"> max = 100 // error TS2322: Type 'number' is not assignable to type 'string'. The expected type comes from property 'maxlength' which is declared here on type 'Partial<Pick<MaxLengthValidator, "maxlength">>'
At first glance, there shouldn’t be any errors since we can mess it up with maxLength
native element property which takes numbers.
But we’re getting an error because of maxlength
input property restriction of MaxLengthValidator directive.
Case with a structural directive:
<>Copy<div *ngFor="let item of {}"></div> error TS2322: Type '{}' is not assignable to type 'NgIterable<any>'.
In the preceding code, the structural directive will be expanded to the full form and we will see input property binding [ngForOf]=”{}”
which leads to the issue.
# Element property bindingsLink to this section
Ivy now can recognize the type of element where we use property binding.Link to this section
For a template like
<>Copy<input type="checkbox" checked={{flag}}>
and property flag
declared as flag = true
in the component we will get:
<>Copyfunction _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = document.createElement("input"); _t1.checked = "" + ctx.checked; // error TS2322: Type 'string' is not assignable to type 'boolean'. } }
Note how compiler defined the element:
<>Copyvar _t1 = document.createElement("input");
Since TypeScript has a mapping from tag names to element type it will result in a type of HTMLInputElement
, not simple HtmlElement
. Just think how cool it is! We now have typesafety for all props and methods of html elements.
What is even more interesting is that this approach can be extended to define custom web components. This required CUSTOM_ELEMENTS_SCHEMA before, but can now leverage full type checking!
In ViewEngine the TCB block looks like:
<>Copyfunction _View_AppComponent_1_0():void { var _any:any = (null as any); const currVal_0:any = i0.ɵinlineInterpolate(1,'',_decl0_12.flag,''); currVal_0; }
As we can see there is no property assignment at all.
# type-safety for any ‘#’ referencesLink to this section
Ivy can understand which directive we’re referring to:
<>Copy{{x.s}} <app-child #x></app-child>
TCB:
<>Copyconst _ctor1: (init: Partial<Pick<i1.ChildComponent, "prop">>) => i1.ChildComponent = (null!); function _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = _ctor1({}); _t1.s; // Property 's' does not exist on type 'ChildComponent'. var _t2 = document.createElement("app-child"); } }
In addition, Ivy compiler knows exactly the type of element with template reference variable assigned:
<>Copy{{x.s}} <input #x type="text">
TCB for this case:
<>Copyfunction _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = document.createElement("input"); _t1.s; // Property 's' does not exist on type 'HTMLInputElement'. } }
# Guard for template context ngTemplateContextGuardLink to this section
This is one of my favorite features. We can create angTemplateContextGuard
static method in a structural directive to keep the correct type of the context for the template that this directive will render.
The ngTemplateContextGuard
method is a user-defined type guard which allows us to narrow down the type of an object within a conditional block.
A widely used NgForOf
directive has ngTemplateContextGuard
as follows:
<>Copystatic ngTemplateContextGuard<T>(dir: NgForOf<T>, ctx: any): ctx is NgForOfContext<T> { return true; }
and also defines NgForOfContext
shape:
<>Copyexport class NgForOfContext<T> { constructor( public $implicit: T, public ngForOf: NgIterable<T>, public index: number, public count: number) {} get first(): boolean { return this.index === 0; } get last(): boolean { return this.index === this.count - 1; } get even(): boolean { return this.index % 2 === 0; } get odd(): boolean { return !this.even; } }
And it actually brings us type-safety for ngFor
.
Let’s look at two examples:
Suppose we render a list of names through ngFor:
<>Copy<div *ngFor="let item of [{ name: '3'}]"> {{ item.nane }} </div>
It will produce the following TCB in Ivy:
<>Copyimport * as i0 from './src/app/app.component'; import * as i1 from '@angular/common'; const _ctor1: <T = any>(init: Partial<Pick<i1.NgForOf<T>, "ngForOf" | "ngForTrackBy" | "ngForTemplate">>) => i1.NgForOf<T> = (null!); function _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = _ctor1({ ngForOf: [{ "name": "3" }] }); var _t2: any = (null!); if (i1.NgForOf.ngTemplateContextGuard(_t1, _t2)) { var _t3 = _t2.$implicit; var _t4 = document.createElement("div"); "" + _t3.nane; } } }
And will give us an error:
error TS2339: Property ‘nane’ does not exist on type ‘{ “name”: string; }’
It works! Magic, right?
Let’s unpack some pieces to understand what’s going on(typescript playground).


- We create
_ctor1
function which takesinit
object as a parameter and returnsNgForOf<T>
generic type. - This means that once we call that
_ctor1
we receiveNgForOf
class of the type we’ve passed to the_ctor1
. So we get_t1: NgForOf<{ name: string; }>
- We use user-defined type guard where we’re passing two variables
_t1
and_t2
declared above. - The goal of the
NgForOf.ngTemplateContextGuard
generic guard is to narrow the second argumentctx
to theNgForOfContext
of the generic type we’ve passed to the first argumentdir: NgForOf<T>
. It’s done by using generic type predicatectx is NgForOfContext<T>.
<>CopyNgForOf.ngTemplateContextGuard(_t1, _t2) / \ NgForOf<{ name: string; }> => NgForOfContext<{name: string;}>
5. It’s now guaranteed that inside if (NgForOf.ngTemplateContextGuard(_t1, _t2)) {
scope the _t2
has NgForOfContext<{name: string;}>
type. It means that _t2.$implicit
returns object of {name: string;}
type.
6. The{name: string;}
type doesn’t have property ‘nane’
declared.
Another interesting case is:
<>Copy<div *ngFor="let item of '3'; let i = 'indix'"></div>
where you will get the error:
error TS2551: Property ‘indix’ does not exist on type ‘NgForOfContext<string>’. Did you mean ‘index’?
since the template will produce the following TCB:
<>Copyconst _ctor1: <T = any>(init: Partial<Pick<i1.NgForOf<T>, "ngForOf" | "ngForTrackBy" | "ngForTemplate">>) => i1.NgForOf<T> = (null!); function _tcb1(ctx: i0.AppComponent) { if (true) { var _t1 = _ctor1({ ngForOf: "3" }); var _t2: any = (null!); if (i1.NgForOf.ngTemplateContextGuard(_t1, _t2)) { var _t3 = _t2.$implicit; var _t4 = _t2.indix; // error TS2551: Property 'indix' does not exist on type 'NgForOfContext<string>'. Did you mean 'index'? var _t5 = document.createElement("div"); } } }
So it restricts names of properties we can use to assign to the local template variable.
We’ve looked at many cases handled by ViewEngine and Ivy compilers.
Let’s recall what is type-checked by Ivy:
- directive inputs
- element methods and properties
- more accurate type-checking for ‘#’ references
- ngFor context
- context of ng-template
Now let’s see where we can find this generated code in case you want to mess around on your own.
Exploring generated type-checking codeLink to this section
Angular CLI uses webpack under the hood and manages a virtual file system internally. This means no file is saved to disk, they are all saved in memory.
All TCBs are generated into synthetic typescript files and hence typescript program can gather diagnostics from them as from any other source file.
The Angular compiler reports errors that refer to synthetic files,
so interpreting the diagnostics and finding the root cause is quite a
challenge.
So, how can we go?
The first solution could be debugging Angular CLI node process. Another option is to change the source code in thenode_modules
folder. But it can be hard for many developers who are not familiar with Angular internals.
There is an alternative hacky way I use when I want to look at the root cause of the issue in a template.
Let’s enable the type-checking feature by editing tsconfig.app.json
and add a section of angularComplierOption
and set the enableFulltemplateCheck
to true
.
<>Copy"angularCompilerOptions": { "fullTemplateTypeCheck": true, }
Then create a simple js file with any name, for example typecheck.js
, in the root folder of your app. We will run this file in NodeJs.
ViewEngine version (Angular CLI 8.1.0) of this file will look like:
<>Copyconst { AngularCompilerPlugin } = require('./node_modules/@ngtools/webpack/src/angular_compiler_plugin.js'); const old = AngularCompilerPlugin.prototype._createOrUpdateProgram; AngularCompilerPlugin.prototype._createOrUpdateProgram = async function() { await old.apply(this, arguments); const sourceFile = this._program.tsProgram.getSourceFiles().find(sf => sf.fileName.endsWith('app.module.ngfactory.ts')); console.log(sourceFile.text); }; require('./node_modules/@angular/cli/bin/ng');
Ivy version (Angular CLI 8.1.0 created with -— enable-ivy
flag):
<>Copyconst { TypeCheckFile } = require('./node_modules/@angular/compiler-cli/src/ngtsc/typecheck/src/type_check_file.js'); const old = TypeCheckFile.prototype.render; TypeCheckFile.prototype.render = function() { const result = old.apply(this, arguments); console.log(result.text); return result; }; require('./node_modules/@angular/cli/bin/ng');
In the code above I’m monkey-patching some internal methods and execute ng
command within this context.
Now, all you need to do is to run the following command in your terminal:
<>Copynode typecheck build --aot
Here I’m executing created above typecheck.js
file with build — aot
parameters.
Note that we can omit --aot
option in Ivy if it’s enabled by default in angular.json
file.


SummaryLink to this section
The type-checking system in Angular is evolving and now Ivy catches lots
of typing bugs which VE does not.
It opens more and more possibilities to improve it but Ivy is still under active development. For example, there is no source mapping enabled yet (but there are some attempts to enable it). And there is no type-safety for HostListener
yet.
I hope this clarified a bit what Angular type-checking looks like.
About the author

Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.

About the author
Alexey Zuev
Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.
About the author

Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.