Angular 123 🅰️ Angular Compiler and Ivy
The Angular compiler transforms templates, components, and decorators into JavaScript that the browser can execute. Since Angular 9, this work is done by Ivy, a complete rewrite of the previous View Engine that changed how compilation works at a fundamental level. Where View Engine compiled components and their NgModules together in a global analysis, Ivy compiles each component locally, using only the information available in the component’s own decorator and its direct imports. This “locality” is the architectural property that makes incremental builds faster, bundle sizes smaller, and error messages clearer.
The compiler itself is a pipeline with distinct phases. It parses TypeScript and extracts Angular metadata from decorators. It builds a graph of NgModules and their scopes. It compiles each component’s template into rendering instructions. It generates TypeScript type-check blocks that let the TypeScript compiler validate template expressions. And it emits JavaScript that contains the compiled component definitions. Each phase has a specific responsibility, and understanding them explains why the compiler behaves the way it does.
This chapter covers the compiler’s role in the Angular build, the difference between Ivy and View Engine, the compilation pipeline from metadata extraction to code generation, the difference between AOT and JIT compilation, template type checking, and the compiler options that control the output.
Key point: The Angular compiler transforms decorators and templates into JavaScript. Ivy, the current engine, compiles components locally using only local information, which enables faster incremental builds and smaller bundles. AOT compilation runs at build time and produces optimized code without the compiler in the bundle; JIT compilation runs in the browser and is used only in specific development scenarios. The compiler generates type-check blocks that TypeScript validates to catch template errors at build time.
Why the compiler exists
The declarative-template problem. Angular templates are not HTML. They contain directives, bindings, pipes, and structural syntax that the browser does not understand. The compiler transforms these templates into JavaScript instructions that create and update the DOM. Without the compiler, the browser would have no way to interpret *ngIf or {{ expression }}.
The performance problem. A template that is compiled at runtime requires a compiler in the browser, which adds to the bundle size and delays the first render. Compiling at build time removes the compiler from the bundle and produces code that the browser can execute immediately. This is the core advantage of AOT compilation.
The type-safety problem. TypeScript type-checks the component class, but it knows nothing about the template. The Angular compiler generates a type-check block (TCB) that expresses the template’s expressions as TypeScript code, which TypeScript then checks. This catches errors like {{ user.nmae }} before the application runs .
The locality problem. View Engine required a global analysis of the entire application to compile a single component, because a component’s template scope depended on its NgModule and that module’s imports. Ivy removes this dependency by embedding the scope information in the component itself. The compiler can now compile a component without looking at any other part of the application .
The ecosystem problem. Libraries are distributed in a compiled format. The compiler’s output format must be stable across versions so that a library built with one version of Angular can be consumed by an application built with another. The compilationMode option controls whether the output is fully compiled (full) or in an intermediate, version-stable format (partial) .
a. Ivy versus View Engine
Ivy and View Engine are the two compilation and rendering engines that Angular has used. View Engine was the default from Angular 4 through Angular 8. Ivy became the default in Angular 9 and replaced View Engine entirely by Angular 12 .
| Aspect | View Engine | Ivy |
|---|---|---|
| Compilation model | Global analysis | Local compilation |
| Component scope | Defined by NgModule | Defined by the component |
| Build overhead | Higher | Lower (~40% reduction) |
| Tree-shaking | Limited | Better |
| Bundle size | Larger | Smaller |
| Error messages | Verbose | Clearer |
| Incremental builds | Slower | Faster |
entryComponents | Required | Not required |
The locality of Ivy is the key difference. In View Engine, a component’s template scope — the set of directives and pipes available in the template — was determined by the NgModule that declared it. The compiler had to analyze the entire NgModule graph to know what a component could use. In Ivy, the scope is expressed in the component’s own compiled definition, so the compiler can work on one component at a time .
This is what enables incremental compilation. When a template changes, only that component needs to be recompiled. When a component’s class changes, only the components that depend on it need to be recompiled. The language service exploits this to provide fast editor feedback as you type .
b. The compilation pipeline
The Angular compiler is a multi-phase pipeline that transforms TypeScript with Angular decorators into JavaScript with compiled component definitions.
Phase 1: Metadata extraction. The compiler reads the TypeScript source and extracts Angular metadata from decorators like @Component, @Directive, @NgModule, and @Pipe. The metadata collector evaluates expressions in the decorator to the extent possible, folding constants and inlining template strings. Expressions that cannot be folded are recorded as an AST for the compiler to resolve later .
The collector can fold literal objects, arrays, strings, numbers, and simple binary and conditional expressions. It cannot fold function calls, new expressions, or spreads in arrays. When an expression cannot be folded, the compiler may rewrite it into an exported variable during a later phase .
Phase 2: Scope analysis. The compiler builds a graph of NgModules, their declarations, imports, and exports. It uses this graph to determine the compilation scope of each component — the set of directives, components, and pipes available in its template. In View Engine this was a global analysis; in Ivy the scope is embedded in the component’s compiled definition .
Phase 3: Template type checking. The compiler generates a type-check block for each component’s template. The TCB is TypeScript code that mirrors the template’s expressions and bindings, allowing TypeScript to validate them. Errors in the TCB are mapped back to the original template location and reported to the developer .
Phase 4: Template compilation. The compiler transforms the template into rendering instructions. These instructions create elements, set properties, attach event listeners, and manage embedded views. The output is an Ivy component definition (a ɵcmp factory) that the runtime executes .
Phase 5: Code generation. The compiler emits JavaScript that contains the compiled component definitions. The output format depends on the compilation mode: full produces fully AOT-compiled code; partial produces an intermediate format suitable for published libraries .
c. AOT versus JIT
AOT and JIT are not different compilers. Angular has one compiler. The difference is when it runs.
| Aspect | AOT | JIT |
|---|---|---|
| When it runs | Build time | Runtime (browser) |
| Compiler in bundle | No | Yes |
| Bundle size | Smaller | Larger |
| Initial render | Faster | Slower |
| Error detection | Build time | Runtime |
| Default since | Angular 9 | Before Angular 9 |
With AOT, the compiler runs during the build and produces JavaScript that contains the compiled templates. The browser downloads and runs the code directly. The Angular compiler is not included in the bundle, which reduces size and eliminates the runtime compilation step .
With JIT, the compiler runs in the browser when the application bootstraps. The compiler is part of the bundle, and templates are compiled at runtime. This was the default before Angular 9 and is now used in specific scenarios where templates are generated dynamically or when the build system cannot run AOT .
Since Angular 9, AOT is the default even in development mode. This means ng serve benefits from the same compile-time checking as production builds, and the developer sees template errors immediately rather than at runtime .
d. Template type checking
Template type checking is one of the compiler’s most valuable features. It catches errors in template expressions that would otherwise surface only when the application runs.
The compiler generates a type-check block (TCB) for each component. The TCB is a TypeScript function that mirrors the template’s bindings and expressions. For a template like <p>Name: {{user.name}}</p>, the TCB contains a statement that accesses ctx.user.name, which TypeScript then checks against the component’s types .
Type narrowing works in templates the same way it works in TypeScript. An *ngIf="person" narrows the type of person within the block, so {{person.address.street}} does not produce an “Object is possibly ‘undefined'” error when the *ngIf guarantees that person is defined .
The strictTemplates compiler option enables the most thorough checking. Without it, the compiler performs basic checks; with it, the checking matches the strictness of the TypeScript compiler’s strict mode .
The non-null assertion operator (!) can be used in templates to suppress “possibly undefined” errors when the developer knows a value is non-null. The Angular documentation recommends using it sparingly, because a refactor can break the constraint that makes it safe .
e. Compiler options
The angularCompilerOptions block in tsconfig.json controls the compiler’s behavior.
| Option | Purpose | Default |
|---|---|---|
strictTemplates | Enable full template type checking | false |
compilationMode | full for applications, partial for libraries | full |
annotationsAs | Emit decorators as static fields or leave them | static fields |
enableI18nLegacyMessageIdFormat | Use legacy i18n message IDs | true |
enableResourceInlining | Inline templates and styles | true for CLI libraries |
disableExpressionLowering | Disable metadata rewriting | true |
The annotationsAs option is relevant for tree-shaking. The default static fields replaces decorators with static fields, which allows advanced tree-shakers like Closure Compiler to remove unused classes. The decorators value leaves decorators in place, which makes compilation faster but prevents effective tree-shaking .
The compilationMode option is the most important for libraries. The partial mode produces a stable intermediate format that is compatible across Angular versions. Libraries published to npm should use partial. Applications and libraries built in the same repository with the same Angular version can use full .
Complete Example Session
// ============================================
// PART 1: A COMPONENT WITH A TEMPLATE
// ============================================
import { Component } from '@angular/core';
@Component({
selector: 'app-hero',
template: `
<h1>{{ title }}</h1>
<p>{{ hero.name }}</p>
<button (click)="increment()">+1</button>
`,
})
export class HeroComponent {
title = 'Hero';
hero = { name: 'Alice', age: 30 };
increment() {
this.hero.age++;
}
}
// ============================================
// PART 2: TYPE CHECK BLOCK (GENERATED)
// ============================================
// The compiler generates a TCB similar to this:
function HeroComponent_TCB(ctx: HeroComponent) {
'' + ctx.title;
'' + ctx.hero.name;
ctx.increment();
}
// TypeScript checks this code and reports errors
// mapped back to the template.
// ============================================
// PART 3: ENABLE STRICT TEMPLATES
// ============================================
{
"angularCompilerOptions": {
"strictTemplates": true
}
}
// ============================================
// PART 4: TEMPLATE ERROR CAUGHT BY TCB
// ============================================
// Template: {{ hero.nmae }}
// Error: Property 'nmae' does not exist on type '{ name: string; age: number; }'
// ============================================
// PART 5: LIBRARY COMPILATION MODE
// ============================================
{
"angularCompilerOptions": {
"compilationMode": "partial"
}
}
// ============================================
// PART 6: APPLICATION COMPILATION MODE
// ============================================
{
"angularCompilerOptions": {
"compilationMode": "full"
}
}
# ============================================
# PART 7: AOT BUILD
# ============================================
ng build --configuration production
# The compiler runs at build time
# Output contains compiled component definitions
# No compiler in the bundle
# ============================================
# PART 8: JIT BUILD (LEGACY)
# ============================================
ng serve
# Since Angular 9, AOT is used even in dev mode
# JIT is only used for specific scenarios
// ============================================
// PART 9: ANNOTATIONS AS STATIC FIELDS
// ============================================
{
"angularCompilerOptions": {
"annotationsAs": "static fields"
}
}
// Enables better tree-shaking
# ============================================
# PART 10: VERIFY COMPILED OUTPUT
# ============================================
# The compiled component definition contains:
# - ɵcmp (component definition)
# - ɵfac (factory function)
# - Template rendering instructions
# These are what the runtime executes
These ten parts cover a component with a template, the generated type-check block, enabling strict templates, a template error caught by the TCB, library compilation mode, application compilation mode, AOT build, JIT build, annotations as static fields, and the compiled output.
Quick Reference
Ivy vs View Engine
| Aspect | View Engine | Ivy |
|---|---|---|
| Compilation | Global | Local |
| Build overhead | Higher | Lower |
| Tree-shaking | Limited | Better |
| Bundle size | Larger | Smaller |
entryComponents | Required | Not required |
| Incremental | Slower | Faster |
Compilation Phases
| Phase | Purpose |
|---|---|
| Metadata extraction | Extract decorator metadata |
| Scope analysis | Build NgModule graph |
| Template type checking | Generate TCB for TypeScript |
| Template compilation | Generate rendering instructions |
| Code generation | Emit JavaScript |
AOT vs JIT
| Aspect | AOT | JIT |
|---|---|---|
| When | Build time | Runtime |
| Compiler in bundle | No | Yes |
| Bundle size | Smaller | Larger |
| Default | Angular 9+ | Before Angular 9 |
Compiler Options
| Option | Purpose | Default |
|---|---|---|
strictTemplates | Full template checking | false |
compilationMode | full or partial | full |
annotationsAs | Decorator emission | static fields |
enableResourceInlining | Inline templates | true (CLI libs) |
Type-Check Block
| Aspect | Detail |
|---|---|
| Purpose | Let TypeScript check templates |
| Output | Synthetic TypeScript function |
| Errors | Mapped back to template |
| Type narrowing | Supported via *ngIf |
Best Practices
✅ Do This:
// Enable strict template checking
{ "angularCompilerOptions": { "strictTemplates": true } }
// Use partial mode for published libraries
{ "angularCompilerOptions": { "compilationMode": "partial" } }
// Make template-bound members public
export class HeroComponent {
title = 'Hero'; // public, accessible in template
}
❌ Don’t Do This:
// Private members in templates
export class HeroComponent {
private title = 'Hero'; // ❌ AOT error
}
// Using full mode for a published library
{ "angularCompilerOptions": { "compilationMode": "full" } } // ❌ version skew
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Template error at runtime | JIT or no strictTemplates | Enable AOT and strictTemplates |
| Private member error | Private member bound in template | Make it public or protected |
| Library version skew | Full mode in published library | Use partial mode |
| Larger bundle | JIT compilation | Use AOT |
| Slow incremental builds | Global compilation (View Engine) | Upgrade to Ivy |
Real-World Examples
1. Strict Templates
{ "angularCompilerOptions": { "strictTemplates": true } }
2. Library Partial Mode
{ "angularCompilerOptions": { "compilationMode": "partial" } }
3. Type Narrowing with *ngIf
<div *ngIf="person">
{{ person.address.street }}
</div>
4. Non-Null Assertion
{{ person.address!.street }}
5. AOT Build
ng build --configuration production
6. Check Compiled Output
grep "ɵcmp" dist/*/main.js
7. Annotations for Tree-Shaking
{ "angularCompilerOptions": { "annotationsAs": "static fields" } }
8. Resource Inlining
{ "angularCompilerOptions": { "enableResourceInlining": true } }
9. View Engine Migration
ng update @angular/core @angular/cli
10. Language Service
{ "angularCompilerOptions": { "strictTemplates": true } }
Visual
Compiler Pipeline
┌──────────────────────────────────────────────────────────────┐
│ TypeScript source with Angular decorators │
│ │ │
│ ▼ │
│ METADATA EXTRACTION │
│ └── Extract @Component, @Directive, @NgModule │
│ │ │
│ ▼ │
│ SCOPE ANALYSIS │
│ └── Build NgModule graph, determine template scope │
│ │ │
│ ▼ │
│ TEMPLATE TYPE CHECKING │
│ └── Generate TCB → TypeScript checks → errors reported │
│ │ │
│ ▼ │
│ TEMPLATE COMPILATION │
│ └── Generate Ivy rendering instructions │
│ │ │
│ ▼ │
│ CODE GENERATION │
│ └── Emit JavaScript with compiled component definitions │
└──────────────────────────────────────────────────────────────┘
Ivy Locality
┌──────────────────────────────────────────────────────────────┐
│ VIEW ENGINE: │
│ Component ──▶ NgModule ──▶ Imports ──▶ Global analysis │
│ └── Cannot compile component without whole app │
│ │
│ IVY: │
│ Component ──▶ Local scope ──▶ Compiled definition │
│ └── Compile component independently │
│ └── Faster incremental builds, better tree-shaking │
└──────────────────────────────────────────────────────────────┘
AOT vs JIT
┌──────────────────────────────────────────────────────────────┐
│ AOT (Angular 9+ default): │
│ Build time: compiler runs → produces compiled JS │
│ Browser: downloads compiled JS → renders immediately │
│ Bundle: no compiler │
│ │
│ JIT (legacy): │
│ Build time: TypeScript compiled to JS │
│ Browser: downloads compiler + templates → compiles → renders│
│ Bundle: includes compiler │
└──────────────────────────────────────────────────────────────┘
Type-Check Block
┌──────────────────────────────────────────────────────────────┐
│ Template: <p>{{user.name}}</p> │
│ │ │
│ ▼ │
│ TCB: function C_TCB(ctx: C) { │
│ '' + ctx.user.name; │
│ } │
│ │ │
│ ▼ │
│ TypeScript checks: ctx.user.name is valid │
│ If invalid: error mapped to template location │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Compiler | Transforms decorators and templates to JS |
| Ivy | Local compilation, Angular 9+ |
| View Engine | Global compilation, deprecated |
| AOT | Build-time compilation, default since Angular 9 |
| JIT | Runtime compilation, legacy |
| Locality | Ivy compiles components independently |
| TCB | Type-check block for template validation |
strictTemplates | Full template type checking |
compilationMode | full for apps, partial for libraries |
annotationsAs | static fields for tree-shaking |
| Incremental builds | Faster with Ivy locality |
Key takeaways:
- The Angular compiler transforms declarative templates into executable JavaScript. Without it, the browser would have no way to interpret directives, bindings, and structural syntax.
- Ivy is the current engine and compiles locally. Each component’s template scope is expressed in its own compiled definition, so the compiler can work on one component at a time. This enables faster incremental builds and better tree-shaking .
- AOT is the default since Angular 9. The compiler runs at build time, the compiler is not included in the bundle, and template errors are caught before the application runs .
- Template type checking uses type-check blocks. The compiler generates TypeScript code that mirrors the template’s expressions, and TypeScript validates it. Errors are mapped back to the template location .
- The
strictTemplatesoption enables full checking. Without it, only basic checks are performed. With it, the checking matches the strictness of TypeScript’sstrictmode . - The
compilationModeoption distinguishes applications from libraries. Applications usefullfor optimized output; published libraries usepartialfor version compatibility . - The
annotationsAsoption controls tree-shaking. The defaultstatic fieldsreplaces decorators with static fields, which allows advanced tree-shakers to remove unused code . - Ivy removed the need for
entryComponents. Components are discovered and compiled automatically from their usage, which simplifies the configuration .
Remember: The Angular compiler is the bridge between the declarative code you write and the JavaScript the browser executes. Ivy, the current engine, is a complete rewrite of View Engine that compiles components locally, which makes builds faster and bundles smaller. The compiler has distinct phases: metadata extraction, scope analysis, template type checking, template compilation, and code generation. AOT compilation runs at build time and is the default since Angular 9; JIT compilation runs in the browser and is legacy. Template type checking catches errors before runtime by generating type-check blocks that TypeScript validates. The compiler options in tsconfig.json control the strictness of checking, the output format for libraries, and the tree-shaking behavior. Understanding the compiler is understanding how Angular turns templates into running applications.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!