Type-checking templates in Angular View Engine and Ivy
In this article we'll explore how Angular type-checks templates, review the difference between View Engine and Ivy type-checking and break down the process of finding and understanding generated type-checking code.

In this article, I will dive into Angular type-checking system, show how it’s done in View Engine 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 View Engine (VE) and Ivy type-checking
- become familiar with new Ivy type-checking features
- get more confident with template errors since you will know how to find and understand 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 1Supplied 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 can that be if templates are html
files?
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:
@Component({
selector: 'app-root',
template: '{{ foo }}'
})
export class AppComponent {}
Here’s how the type check block looks like for this component:
var _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 now look at how it’s handled by View Engine and Ivy.
Type-checking in View Engine
You probably know 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:
@NgModule({
...
declarations: [
AppComponent
]
})
export class AppModule {}
will produce synthetic file like:
import * 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? As it turns out quite a few things.
component member access
In case we forgot to declare some property or method in component Angular’s type-checking system will produce a diagnostic:
{{ unknownProp }}
The produced TCB looks like:
var _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 bindings
The compiler will remind us that we forgot to add arguments to the method executed from template
@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
.
var _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
Consider the following code in your component:
@HostListener('click', ['$event'])
onClick() {}
The compiler will generate TCB as follows:
function _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:
{{ getSomething() }}
{{ obj[prop][subProp] }}
{{ someMethod({foo: 1, bar: '2'}) }}
type-checking for pipe
The types of the pipe’s value and arguments are matched against the transform()
call.
<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 ‘#’
<div aDir #aDir="aDir">{{aDir.fname}}</div>
Property 'fname' does not exist on type 'ADirective'. Did you mean 'name'?
$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
Ivy type-checking
Remember, in View Engine 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:
static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, ‘ngForOf’|’ngForTrackBy’|’ngForTemplate’>>): NgForOf<T>;
A typical usage would be:
NgForOf.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.
class 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 inputs
It’s now possible to get an error if you’ve passed a property of a wrong type to a directive:
<app-child [prop]="'text'"></app-child>
export class ChildComponent implements OnInit {
@Input() prop: number;
In VE the generated code looks like:
function _View_AppComponent_1_0():void {
var _any:any = (null as any);
const currVal_0:any = 'text';
currVal_0;
}
Ivy brings us improved TCB:
const _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:
<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:
<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 bindings
Ivy now can recognize the type of element where we use property binding. For a template like:
<input type="checkbox" checked={{flag}}>
and property flag
declared as flag = true
in the component we will get:
function _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:
var _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 View Engine the TCB block looks like:
function _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 ‘#’ references
Ivy can understand which directive we’re referring to:
{{x.s}}
<app-child #x></app-child>
TCB:
const _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:
{{x.s}}
<input #x type="text">
TCB for this case:
function _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 ngTemplateContextGuard
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:
static ngTemplateContextGuard<T>(dir: NgForOf<T>, ctx: any): ctx is NgForOfContext<T> {
return true;
}
and also defines NgForOfContext
shape:
export 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:
<div *ngFor="let item of [{ name: '3'}]">
{{ item.nane }}
</div>
It will produce the following TCB in Ivy:
import * 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>.
NgForOf.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 here:
<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:
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: "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.
In this article we’ve looked at many cases handled by View Engine 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 code
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 still do it?
The first solution could be debugging Angular CLI node process. Another option is to change the source code in the node_modules
folder. But it can be hard for many developers who are not familiar with Angular internals.
There is an alternative hacky way however 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
.
"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:
const { 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):
const { 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:
node 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.

Summary
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 article clarified a bit what Angular type-checking looks like.