Open-Source Wikis

/

Angular

/

Systems

/

Ivy runtime

angular/angular

Ivy runtime

The instruction-based rendering engine that replaced View Engine in v9. Implemented in packages/core/src/render3/.

Mental model

A compiled component template is a function over an LView. The function is called twice per change-detection cycle:

  1. Create pass (RenderFlags.Create): the first time the template runs for an instance, instructions like ɵɵelementStart, ɵɵtext, ɵɵtemplate create DOM nodes and store them in the LView's element slots.
  2. Update pass (RenderFlags.Update): on every subsequent tick, the same function runs in update mode and instructions like ɵɵproperty re-evaluate bindings against current state, comparing each binding against its previous value and only writing to the DOM when something changed.

This is instruction generation (the compiler emits a function calling instructions) coupled with instruction execution (the runtime walks the function call graph and updates the DOM).

Core data structures

graph LR
  TView["TView<br/>(static blueprint)"] -.->|shared by| LView["LView (instance N)"]
  LView --> Context["Component instance / state"]
  LView --> Bindings["Binding values<br/>(prev + curr per slot)"]
  LView --> RNodes["DOM node refs"]
  LView --> Children["Child LViews / LContainers"]
  • TView (packages/core/src/render3/interfaces/view.ts) — the static, compile-time-derived metadata for a template. Shared across every instance of a given component. Holds TNode arrays, the consts table, and feature flags.
  • LView — the per-instance, mutable array. Position 0 is the parent reference; positions thereafter alternate between component context, binding slots, child views, and DOM node references depending on the matching TNode.
  • TNode — describes a position in the template (an element, a container, a projection). One TNode per position; shared across instances via the TView.
  • LContainer — a slot in an LView that holds dynamically created views. Used by structural directives, *ngFor, @for, view containers, deferrable views.

Why a flat array? Speed and memory locality. Each instruction knows the slot index it operates on at compile time, so updates are O(1) array accesses.

The instruction set

The compiler emits calls to instructions in packages/core/src/render3/instructions/. Highlights:

Instruction Purpose
ɵɵelementStart / ɵɵelementEnd Open and close an element (with attributes, listeners).
ɵɵelement Self-closing element shorthand.
ɵɵtext / ɵɵtextInterpolate* Static and interpolated text nodes.
ɵɵproperty Update a DOM property binding.
ɵɵattribute Update an attribute binding.
ɵɵlistener Register an event listener.
ɵɵtemplate Embedded view definition (used by *ngIf, *ngFor, @if, @for).
ɵɵprojection / ɵɵprojectionDef Content projection (<ng-content>).
ɵɵdirectiveInject DI lookup for a directive constructor.
ɵɵpipe / ɵɵpipeBind Pipe instantiation and invocation.
ɵɵdefer* Defer block lifecycle.
ɵɵrepeater* The @for block instructions.
ɵɵconditional The @if / @switch block instruction.

The full reference is the contract documented in packages/core/src/render3/CODE_GEN_API.md. The compiler treats this contract as sacred — emit changes require runtime changes.

Execution lifecycle

sequenceDiagram
  participant Boot as bootstrapApplication
  participant App as ApplicationRef
  participant CD as Scheduler
  participant Tmpl as Template fn
  participant DOM
  Boot->>App: createComponent(rootDef)
  App->>App: allocate LView, link to TView
  App->>Tmpl: render(LView, RenderFlags.Create)
  Tmpl->>DOM: ɵɵelementStart, ɵɵtext, ...
  loop on each tick
    CD->>App: tick()
    App->>Tmpl: render(LView, RenderFlags.Update)
    Tmpl->>Tmpl: per-slot binding compare
    Tmpl->>DOM: ɵɵproperty(value) when changed
  end

The scheduler is the connective tissue. In zone-based apps it's driven by NgZone.onMicrotaskEmpty; in zoneless apps it's driven explicitly by markForCheck, signal updates, and route transitions. See change-detection for the full picture.

Why this is fast

Three optimization layers:

  1. Tree-shakability. Each instruction is a separate exported function. Bundlers drop the ones a particular template doesn't call. Apps that don't use @for get no ɵɵrepeater code.
  2. Monomorphism. All LViews are arrays with the same shape. V8 can JIT-compile bindings access into machine instructions of constant cost.
  3. Minimal allocations on the hot path. LView slots reuse fixed positions across ticks. Bindings comparisons live in adjacent array slots so they hit the same cache line.

The constraints are documented in packages/core/src/render3/PERF_NOTES.md. When changing the runtime, verify you haven't accidentally added an allocation in the hot path or broken the monomorphism assumption.

Integration with the compiler

graph LR
  Source["@Component decorator + template"] --> NgTsc["ngtsc<br/>(annotations/component)"]
  NgTsc --> CompilerLib["@angular/compiler<br/>(parse + IR)"]
  CompilerLib --> ViewCompiler["render3/view/compiler.ts"]
  ViewCompiler --> Output["o.Expression IR"]
  Output --> Translator["compiler-cli/transform/translator.ts<br/>(IR → TS)"]
  Translator --> JS["Component.ɵcmp = ɵɵdefineComponent({...})"]
  JS --> Runtime["runtime: this very runtime"]

The contract: the compiler emits ɵɵdefineComponent(...) (and friends), which produces a ComponentDef containing a template function and a consts table. The runtime looks at that def, allocates a matching TView, and starts executing.

Reactivity integration

When a component reads a signal during the update pass, the runtime registers the active LView as a consumer of that signal via ReactiveLViewConsumer (packages/core/src/render3/reactive_lview_consumer.ts). When the signal changes, the consumer marks the LView dirty without going through the full top-down dirty-marking that a non-signal binding requires.

This is what makes zoneless mode efficient: signal-driven views update precisely the LViews that depend on a signal, rather than walking the whole tree on every tick.

Reading the source

The most useful files when learning the runtime, in suggested reading order:

  1. packages/core/src/render3/interfaces/view.tsTView/LView shapes.
  2. packages/core/src/render3/state.ts — the global "current LView" stack used during execution.
  3. packages/core/src/render3/instructions/element.ts — concrete instruction implementations.
  4. packages/core/src/render3/instructions/change_detection.ts — the per-LView update pass driver.
  5. packages/core/src/render3/node_manipulation.ts — DOM mutation helpers.

The reference-core project skill (/skills/reference-core) walks through the same material in a curated order — open it via the Skill tool when working in this code.

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

Ivy runtime – Angular wiki | Factory