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:
- Create pass (
RenderFlags.Create): the first time the template runs for an instance, instructions likeɵɵelementStart,ɵɵtext,ɵɵtemplatecreate DOM nodes and store them in theLView's element slots. - Update pass (
RenderFlags.Update): on every subsequent tick, the same function runs in update mode and instructions likeɵɵpropertyre-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. HoldsTNodearrays, 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 matchingTNode.TNode— describes a position in the template (an element, a container, a projection). OneTNodeper position; shared across instances via theTView.LContainer— a slot in anLViewthat 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
endThe 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:
- Tree-shakability. Each instruction is a separate exported function. Bundlers drop the ones a particular template doesn't call. Apps that don't use
@forget noɵɵrepeatercode. - Monomorphism. All
LViews are arrays with the same shape. V8 can JIT-compile bindings access into machine instructions of constant cost. - Minimal allocations on the hot path.
LViewslots 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:
packages/core/src/render3/interfaces/view.ts—TView/LViewshapes.packages/core/src/render3/state.ts— the global "current LView" stack used during execution.packages/core/src/render3/instructions/element.ts— concrete instruction implementations.packages/core/src/render3/instructions/change_detection.ts— the per-LView update pass driver.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.