Angular 21 · 7 min read · August 7, 2026
Angular's control-flow directives — @if, @for, @switch — don't just add and remove elements. They leave behind invisible anchor nodes in the DOM, and those anchors are the only way Angular knows where to put content back when a condition changes. If you destroy one, the directive silently breaks — and no error tells you.
What is an anchor node?
When Angular compiles a template with @if, it doesn't simply render or remove your element. It inserts a DOM comment node at the exact position where the conditional content should appear. This comment is the anchor.
When the condition is true, Angular inserts the rendered content before the anchor. When the condition flips to false, Angular removes the content but leaves the anchor in place. That way, when the condition becomes true again, Angular walks to the anchor and re-inserts the content at the correct position.
You can see this for yourself in the demo app.
Toggle
show to false and look at the DOM inspector: the paragraph disappears, but a comment node remains. That comment is the anchor, and it's the whole reason the directive can restore content later.
When appendChild() is safe
appendChild() adds a new node at the end of the parent's child list. Crucially, it does not touch any existing children. The anchor comment stays right where it was. So after appending, the @if directive still works: toggle the condition off and on, and the paragraph comes back exactly where it should.
This is the key insight: adding nodes is safe; wiping nodes is not. As long as you don't destroy the anchor, Angular's bookkeeping stays intact.
When replaceChildren() breaks everything
replaceChildren() is more aggressive. It removes every existing child of the element before inserting its arguments. That includes text nodes, element nodes — and comment nodes. The anchor is gone.
Now flip show to false. Angular removes the paragraph. Then flip it back to true. Angular looks for its anchor to know where to insert the content… and finds nothing. The directive can't place its content, so nothing renders. No error is thrown. No warning is logged. The paragraph is simply gone, and the only way to get it back is to reload the page.
This is what makes the bug so insidious: it's silent. Your application looks broken to the user, but the console is clean. You might spend hours checking your signals, your conditions, your change detection — all while the real problem is a missing DOM comment that was destroyed three interactions ago.
The same trap applies elsewhere
innerHTML = '',removeChild()on the wrong node,textContent = ''— any operation that wipes a container's children will destroy anchor nodes. The@forand@switchdirectives use the same anchor mechanism, so they're equally vulnerable.
How to recover without a page reload
Once the anchor is destroyed, the @if directive instance is permanently broken. You can't restore the anchor by setting the condition back to true — Angular has no way to recreate it. The only fix is to destroy and recreate the entire directive instance.
In the demo, the Reset button does this by wrapping the host element in a @for loop keyed on a counter. Each reset increments the counter, which forces Angular to tear down the old template — broken @if and all — and build a fresh one with a brand-new anchor. The paragraph comes back immediately, no reload needed.
In a real application, you'd rarely need this. The point is to understand what happened: the directive's internal state was corrupted by an external DOM mutation, and the only recovery is to let Angular rebuild that part of the view from scratch.
The rule
Never call replaceChildren(), innerHTML = '', or any wipe-and-replace operation on an element that Angular manages with control-flow directives. If you need to add content, use appendChild(). If you need to remove content, let Angular handle it through its own reactive state. The DOM inside a directive-managed element is not yours to wipe — it's Angular's, and the anchors are load-bearing.
Why this matters
Modern Angular gives you powerful tools for directly manipulating the DOM when you need them — ViewChild, ElementRef, and raw browser APIs are all available. But with that power comes a contract: the DOM inside directive-managed regions is shared territory. Angular relies on invisible infrastructure — anchor comments, view containers, internal bookkeeping — that lives in the same DOM tree you're touching.
The best defense is understanding what's under the hood. Once you know the anchor is there, you know why appendChild() is safe and replaceChildren() is not. And when something silently stops rendering, you'll know exactly where to look.
Top comments (0)