In an Angular <ng-template>, what do let-item and let-i="index" declare, and how do they differ from #ref and @let?
answer
- only on ng-template
- values come from the context
- no value means $implicit
- fed by whoever renders it
- read-only in handlers
basics
~10 sOn an Angular <ng-template>, let-item binds the context's $implicit value and let-i="index" its index property: read-only inputs supplied by the renderer, unlike #ref (a template target) or @let (a named expression).
solid answer
~40 s`let-` attributes declare **template input variables**, and only on `<ng-template>`; elsewhere the compiler reports that `let-` is only supported on ng-template elements. `let-item` with no value maps to the context object's `$implicit` property; `let-i="index"` maps to its `index` property. The values come from the **context** that the code rendering the template passes in, for example a structural directive, `NgTemplateOutlet` or `createEmbeddedView`, so the same template can show different data each time it is rendered. That differs from `#ref`, which points at something declared in the template (an element, component, directive or `TemplateRef`), and from `@let`, which names an expression the template itself evaluates. `let-` variables, like `@let`, are read-only in handlers.
code
ts · 29 linesimport {Component, input, TemplateRef} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
interface Product { id: string; name: string; }
@Component({
selector: 'app-product-list',
imports: [NgTemplateOutlet],
template: `
<ul>
@for (p of products(); track p.id; let i = $index) {
<ng-container
[ngTemplateOutlet]="rowTemplate()"
[ngTemplateOutletContext]="{$implicit: p, position: i + 1}"
/>
}
</ul>
`,
})
export class ProductList {
readonly products = input.required<Product[]>();
readonly rowTemplate = input.required<TemplateRef<unknown>>();
}
// Consumer template:
// <ng-template #rowTpl let-product let-pos="position">
// <li>{{ pos }}. {{ product.name }}</li>
// </ng-template>
// <app-product-list [products]="items()" [rowTemplate]="rowTpl" />go deeper
Recall that let- goes on ng-template, that a bare let-x means $implicit, and that the renderer supplies the values.
Contrast the three variable kinds by source of value and place of declaration, and map let-x="key" to a context property.
Use let- inputs to build reusable row or cell templates handed to components, and keep context keys stable and typed.
Decide when a template-injection API is worth its complexity compared with content projection or a dedicated child component.
## Three kinds of template variables Angular templates have three ways to introduce a name, and interviewers like to see them told apart: | Syntax | Name for | Where the value comes from | Where it can be declared | |---|---|---|---| | `#row` | an element, component, directive (via `exportAs`) or `TemplateRef` | the thing it is declared on | any element | | `@let total = expr;` | the result of an expression | the template evaluates it | anywhere in the template | | `let-item`, `let-i="index"` | a property of the rendering **context** | the code that renders the template | only on `<ng-template>` | ## How let- variables work An `<ng-template>` is a fragment that is not rendered where it is written. Something else renders it, once or many times, and each time it may pass a **context object**. `let-` attributes declare which context properties the fragment wants, and under which local names: - `let-item` (no value) binds the context's **`$implicit`** property; - `let-i="index"` binds the context's **`index`** property to the local name `i`; - any number can be declared: `let-row let-i="index" let-last="last"`. ```html <ng-template #rowTpl let-product let-pos="position"> <li>{{ pos }}. {{ product.name }}</li> </ng-template> ``` If this template is rendered with a context `{ $implicit: someProduct, position: 3 }`, then `product` is `someProduct` and `pos` is `3`. Rendered with another context, the same fragment shows other data. The names are local to the fragment's view and to views nested inside it. Compiler rules: 1. `let-` is only allowed on `<ng-template>`; on any other element it is a template error. 2. The local name may not contain `-`. 3. The variable is **read-only**: assigning to it in an event handler is reported with `Template variables are read-only`. ## Where the context comes from This leaf only needs the shape; the rendering APIs belong elsewhere. In short: - a **structural directive** creates the context and the asterisk syntax desugars into an `<ng-template>` with `let-` attributes; this is how `*ngFor="let item of items; let i = index"` works; - **`NgTemplateOutlet`** takes the context through its context input; - **`ViewContainerRef.createEmbeddedView(templateRef, context)`** passes it programmatically. A fragment still sees the component's members and the variables of the view where it was **declared**, in addition to its `let-` inputs. ## Contrasting with #ref and @let - A **`#ref` on the `<ng-template>` itself**, as in `#rowTpl`, is how other code gets the `TemplateRef` to render; it says nothing about the data inside. - A **`@let`** inside the fragment can derive values from the `let-` inputs: `@let label = pos + '. ' + product.name;`. - `let-` variables are the only ones whose values are **supplied from outside** the template text, which is what makes a fragment reusable. ## Typing and modern alternatives With strict template checks, `let-` variables are typed from the context type the renderer declares, or `any` when it cannot be inferred; typing a custom directive's context is a structural-directive topic. In current Angular, the built-in `@for` and `@if` blocks declare their variables in the block header instead, so `let-` attributes appear mainly with reusable fragments passed to components, such as a row template given to a table component. ## Common mistakes - Putting `let-x` on a `<div>`, expecting it to work like a local variable. - Expecting `let-x="item"` to read a component field named `item`; it reads the **context** property `item`. - Forgetting that `let-x` with no value is `$implicit`, then passing the data under another key.
- In an Angular <ng-template>, what does let-x="item" read when the component also has an item property?It reads the `item` property of the context object passed by whoever renders the template, not the component's field. If the context has no `item` key, `x` is `undefined`. To read the component field inside the fragment, use `item` directly, since the fragment still sees the declaring component's members.
- Can an Angular let- template variable be changed from an event handler inside the fragment?No. Template input variables are read-only, and the template checker reports an assignment to one. To change the underlying data, call a component method or update the source that supplies the context, and the next render of the fragment receives the new value.
saying these in an interview costs you the question
- let- attributes work on any element as local variables
- let-item with no value reads a context key named item
- let-x="name" reads the component's name property
- A #ref on ng-template gives the fragment's context data
- let- variables can be reassigned inside the fragment