skip to content

Why does Angular reject `*ngIf` and `*ngFor` on the same element, and how do you apply both structural directives?

level: juniorimportance: should knowfreq 48%

answer

  1. one wrapper per element
  2. which wrapper goes outside?
  3. a grouping element with no DOM node
  4. compile error, not a runtime one

basics

~20 s

Each asterisk expands into an ng-template around the element, and one element can only have one such wrapper, so the compiler rejects a second starred directive; nest them, putting the outer one on an ng-container.

solid answer

~40 s

The asterisk shorthand turns the element into the content of a single `<ng-template>`. Two starred directives would need two nested templates, and the compiler has no way to know which should be outside: filter then repeat, or repeat then filter. So it reports a template error: "Can't have multiple template bindings on one element. Use only one attribute prefixed with *". You choose the nesting yourself, usually by moving one directive to an `<ng-container>`, which groups content without adding a DOM element: `<ng-container *ngFor="let u of users"><li *ngIf="u.active">…</li></ng-container>`. In current code the same need is met with `@for` and `@if` blocks, which nest naturally, but the one-per-element rule still applies to custom structural directives.

go deeper

for a junior

Recall that only one starred directive fits on an element and that ng-container is the usual way to add the second one.

for a middle

Explain the ambiguity: each asterisk needs its own ng-template, and the nesting order changes what the condition can see.

for a senior

Pick the nesting deliberately, avoid extra DOM elements that break markup, and consider moving filtering into a computed value instead.

for a principal

Use the rule when setting template conventions: control-flow blocks for conditions and loops, and at most one custom structural directive per element.

## The rule In an Angular template you can put **only one asterisk-prefixed directive on an element**. Writing both `*ngFor` and `*ngIf` on one `<li>`, or a custom `*appHasRole` next to `*ngIf`, is rejected by the compiler with the error: > Can't have multiple template bindings on one element. Use only one attribute prefixed with * It is a **compile-time template error**, so it fails the build as soon as the template is compiled rather than surfacing as a runtime surprise. ## Why the compiler refuses The asterisk is shorthand for wrapping the element in an `<ng-template>` that carries the directive. With one starred attribute the expansion is unambiguous. With two, the compiler would have to generate two nested templates, and the order changes the meaning: 1. **Repeat outside, condition inside**: create a row per user, then decide per row whether to render it. The condition can use the loop variable. 2. **Condition outside, repeat inside**: decide once whether to render the whole list, then repeat. The condition cannot see a loop variable. Neither order is obviously right, and guessing would silently change behaviour, so Angular requires the author to state the nesting explicitly. The documentation gives exactly this reason: there is only one `<ng-template>` onto which the directive is unwrapped, and it is unclear which directive should come first. ## How to combine them The standard fix is to move one directive onto an **`<ng-container>`**, an Angular grouping element that renders no DOM node of its own: ```html <ul> <ng-container *ngFor="let u of users"> <li *ngIf="u.active">{{ u.name }}</li> </ng-container> </ul> ``` Here the loop is outside and the condition inside, so each `u` is filtered individually and the `<ul>` still contains only `<li>` children. Other options: - **Swap the nesting** when the condition is about the whole list: put `*ngIf` on the `<ng-container>` and `*ngFor` on the `<li>`. - **Write the long form**: nest explicit `<ng-template>` elements yourself; it is more verbose but shows the structure plainly. - **Filter in the component**: a `computed()` that returns only active users removes the inner condition entirely, which is often clearer than filtering in the template. - **Use control-flow blocks** in new code, described below. | Approach | Extra DOM element | Condition sees loop variable | | --- | --- | --- | | `<ng-container *ngFor>` with `*ngIf` inside | No | Yes | | `<ng-container *ngIf>` with `*ngFor` inside | No | No | | Wrapping `<div>` instead of `<ng-container>` | Yes | Depends on nesting | | Pre-filtered list from `computed()` | No | Not needed | A wrapping `<div>` also works but adds an element, which can break markup such as `<ul>`, `<tr>` or flex layouts, so `<ng-container>` is preferred. ## A worked diagnosis A typical report: "after adding a permission check, the list stopped compiling". The template had `<li *ngFor="let p of projects" *appHasRole="'admin'">`. The fix depends on what the check should mean: 1. **Hide every row from non-admins**: the role check does not depend on the row, so put it outside: `<ng-container *appHasRole="'admin'">` around the whole loop, which also avoids running the check once per row. 2. **Hide only some rows**: if the check depended on each project, the loop goes outside and the check inside, on the `<li>`. 3. **Move the decision out of the template** when possible: if the data can be filtered in the component, a derived list removes one directive entirely. Asking "does the condition need the loop variable?" is the fastest way to choose the nesting, and it is the reasoning interviewers want to hear rather than "wrap it in something". ## In Angular 22 `NgIf` and `NgFor` are deprecated since v20; new code uses `@for (u of users; track u.id) { @if (u.active) { … } }`, where the nesting is written out and the problem never arises. The rule still matters for two reasons: most production templates still contain starred built-ins, and any **custom** structural directive, such as a permission check, is applied with the asterisk and so is still limited to one per element. Combining `*appHasRole` with a loop means an `<ng-container>` or a control-flow block around it.

  • Why prefer `<ng-container>` over a wrapping `<div>` when splitting two structural directives?
    `<ng-container>` is not rendered to the DOM, so it adds no element. A `<div>` inside a `<ul>`, `<table>` or flex container changes the markup and can break semantics or layout. The container only exists to host the extra directive.
  • Is putting `*ngIf` on an element inside a `@for` block allowed?
    Yes. The block is not a structural directive, so the element carries only one starred attribute. It is legal but mixes styles; in new code an `@if` block inside the `@for` is the consistent choice.

saying these in an interview costs you the question

  • Angular applies the directives left to right, so attribute order decides the nesting.
  • The second starred directive is silently ignored at runtime.
  • Wrapping in a div is the only way to combine the two directives.
  • The rule only applies to NgIf and NgFor, not to custom structural directives.