Open-Source Wikis

/

Angular

/

Systems

/

Template compiler

angular/angular

Template compiler

The end-to-end pipeline that turns @Component({template: '...'}) into Ivy ɵcmp definitions on the class. Spans three packages: @angular/compiler, @angular/compiler-cli, and the linker.

Mental model

The compiler is a TypeScript transformer (ngtsc) layered on top of a TypeScript compilation. As tsc walks the program, ngtsc recognizes Angular decorators, parses templates, generates Ivy IR, and rewrites the source so the compiled output contains ɵcmp static fields.

Three layers, three packages:

graph TD
  TSProgram["ts.Program (TypeScript)"] --> Ngtsc["@angular/compiler-cli/src/ngtsc<br/>(annotation handlers, TCBs, transforms)"]
  Ngtsc --> Compiler["@angular/compiler<br/>(template parse, IR build, output)"]
  Compiler --> RuntimeIR["Output IR<br/>(o.Expression nodes)"]
  Ngtsc --> Translator["transform/translator<br/>(o.Expression → TypeScript AST)"]
  Translator --> Emit["TypeScript emit<br/>(.js / .d.ts)"]

  RuntimeIR -.-> Translator
  Linker["compiler-cli/linker<br/>(Babel plugin)"] --> RuntimeIR

@angular/compiler is the language-aware part: HTML parsing, expression parsing, IR generation. @angular/compiler-cli is the TypeScript-aware part: it integrates with tsc, runs annotation handlers, generates type-check blocks, and emits the lowered code.

The linker is the same compiler running in a different host: it consumes "partial" Ivy code (a stable IR shipped on npm) and finishes compilation in the consumer's bundler.

Annotation handlers

Each Angular decorator has a corresponding handler under packages/compiler-cli/src/ngtsc/annotations/:

Decorator Handler
@Component annotations/component/
@Directive annotations/directive/
@Pipe annotations/pipe/
@NgModule annotations/ng_module/
@Injectable annotations/injectable/

A handler implements three phases:

  1. analyze(node) — read the decorator's metadata. Resolves templateUrl, evaluates expressions statically via the partial_evaluator, derives the directive's selector and inputs/outputs.
  2. resolve(node) — link cross-class references: which directives match in this component's template, which providers come from which module, which standalone imports apply.
  3. compile(node) — produce output IR (a o.Expression tree). For components this calls into compileComponentFromMetadata in @angular/compiler.

The output IR is then lowered to TypeScript AST nodes by packages/compiler-cli/src/ngtsc/transform/, and the result becomes part of the tsc emit.

Template parsing

Inside @angular/compiler:

graph LR
  Template["Template string"] --> Tokenizer["ml_parser/lexer.ts"]
  Tokenizer --> HtmlAST["HTML AST"]
  HtmlAST --> R3Parse["render3/view/template.ts<br/>parseTemplate()"]
  R3Parse --> ExprLex["expression_parser/lexer.ts"]
  ExprLex --> ExprParse["expression_parser/parser.ts"]
  ExprParse --> ExprAST["Binding AST"]
  R3Parse --> TplAST["Template AST<br/>(blocks, elements, bindings)"]
  TplAST --> Bind["t2_binder<br/>(directive matching)"]
  Bind --> Build["TemplateDefinitionBuilder<br/>(IR construction)"]
  Build --> IR["Render3 view IR"]

Block syntax (@if, @for, @switch, @let, @defer) became dedicated AST node types during the v17 push and now flows through this pipeline like any other template construct.

Type checking

packages/compiler-cli/src/ngtsc/typecheck/ generates type check blocks (TCBs): synthetic TypeScript expressions that mirror template binding semantics. For a component template:

<button (click)="save(item)" [disabled]="!form.valid">Save</button>

The compiler emits something like:

function _tcb1(this: MyComponent) {
  const button = document.createElement('button');
  // model the binding `[disabled]="!form.valid"`:
  button.disabled = !this.form.valid;
  // model the binding `(click)="save(item)"`:
  ((event: Event) => this.save(item))(null!);
}

tsc runs over the TCBs as part of normal type checking. Errors in template code show up as ordinary TypeScript diagnostics, but the diagnostics are remapped back to the original template offsets via source-map metadata. The same mechanism powers @angular/language-service's editor diagnostics.

strictTemplates and its sub-flags toggle how strictly the TCB encodes inputs (e.g., strictInputTypes, strictOutputEventTypes, strictNullInputTypes).

Partial compilation and the linker

Libraries on npm ship as partially compiled Ivy code via compilationMode: "partial". The output is structurally similar to full Ivy but version-stable: the compiler emits known, narrow shapes (ɵɵngDeclareComponent, ɵɵngDeclareDirective) that the linker knows how to expand into the runtime form for whatever Angular version the consumer pins.

The linker (packages/compiler-cli/linker/) runs as a Babel plugin in the consumer's bundler step. It re-uses the same compileComponentFromMetadata machinery as the AOT compiler — the only difference is where the metadata comes from.

Incremental compilation

ngtsc caches across compiles via packages/compiler-cli/src/ngtsc/incremental/. The cache tracks:

  • File-level changes (which .ts files have new content).
  • Symbol-level changes (which exported types changed).
  • Decorator-level dependencies (which other classes' analysis depends on this class).

Watch builds (ng build --watch, pnpm adev) re-run only the analysis affected by the diff.

Resource resolution

External templateUrl and styleUrls are loaded via packages/compiler-cli/src/ngtsc/resource/. The host (Bazel, the CLI, or the language service) provides a resource loader; the compiler caches the result and re-evaluates on changes.

Integration with the runtime

The contract between compiler output and runtime input is documented in packages/core/src/render3/CODE_GEN_API.md. When a runtime change (new instruction, new feature flag) lands, the compiler is updated in the same release to emit the new shape. Older partial-compiled libraries continue to work because the linker keeps the older mapping available.

Where to start when modifying

The deep mental model is in the reference-compiler-cli project skill — open it via the Skill tool.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Template compiler – Angular wiki | Factory