Understanding the difference between recreating views and moving DOM elements
Have you ever moved a component from one part of your Angular layout to another, only to watch all its state disappear? The timer resets, the form clears, the toggle switches back—everything vanishes. Frustrating, right?
This isn't a bug. It's how Angular handles component rendering. But here's the thing: most developers don't realize there are multiple ways to move components in Angular, and they behave very differently. Some recreate your component from scratch. Others actually move the live instance, preserving all state.
In this article, we'll explore three approaches to moving components across your layout: ngTemplateOutlet, CDK Template Portal, and CDK DOM Portal. By the end, you'll understand exactly when Angular recreates your views and how to preserve component state when moving elements across your application.
Let's dive into a real-world scenario and see what's actually happening under the hood.
The Problem: Moving Components Kills State
Imagine you're building an admin dashboard with a promotional banner. This banner has interactive elements: a heart button users can click and a live timer counting up. Initially, the banner displays in the sidebar, but users can click a button to move it to the main content area.
Here's what happens with a naive implementation:
import { Component, signal } from '@angular/core';
import { PromoBannerComponent } from './promo-banner.component';
@Component({
selector: 'app-root',
imports: [PromoBannerComponent],
styles: [
`
.bottom {
display: flex;
justify-content: center
align-items: center;
height: 100vh;
}
`,
],
template: `
<div class="dashboard">
<header>
<button (click)="toggleRegion()">Toggle Banner Position</button>
</header>
<div class="layout">
@if (dockRight()) {
<main>
<app-promo-banner />
</main>
}
<aside class="bottom">
@if (!dockRight()) {
<app-promo-banner />
}
</aside>
</div>
</div>
`
})
export class AppComponent {
dockRight = signal(false);
toggleRegion() {
this.dockRight.update(value => !value);
}
}
When you click the toggle button, the banner moves—but the heart button resets and the timer starts over from zero. Why?
Because you're not actually moving the component. You're destroying it in one location and creating a brand new instance in another. The conditional rendering removes the component from the DOM entirely, then Angular initializes a fresh instance elsewhere.
Think of it like moving houses. You're not picking up your house and relocating it—you're demolishing it, then building an identical new house somewhere else. Everything inside gets lost in the process.
Can we do better? Let's explore three different approaches.
Approach 1: Using ngTemplateOutlet
The first instinct might be to use Angular's ngTemplateOutlet directive. This lets you define a template once and stamp it out in multiple locations. Sounds promising, right?
Here's how you'd implement it:
import { Component, signal } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { PromoBannerComponent } from './promo-banner.component';
@Component({
selector: 'app-root',
imports: [NgTemplateOutlet, PromoBannerComponent],
styles: [
`
.bottom {
display: flex;
justify-content: center
align-items: center;
height: 100vh;
}
`,
],
template: `
<div class="dashboard">
<header>
<button (click)="toggleRegion()">Toggle Banner Position</button>
</header>
<ng-template #promoBanner>
<app-promo-banner />
</ng-template>
<div class="layout">
@if (dockRight()) {
<main>
<ng-template [ngTemplateOutlet]="promoBanner" />
</main>
}
<aside class="bottom">
@if (!dockRight()) {
<ng-template [ngTemplateOutlet]="promoBanner" />
}
</aside>
</div>
</div>
`
})
export class AppComponent {
dockRight = signal(false);
toggleRegion() {
this.dockRight.update(value => !value);
}
}
You define the banner component once inside an ng-template with a template reference variable. Then you use ngTemplateOutlet to render it in either location based on the condition.
This feels like it should work. You're reusing the same template definition, so surely the state should persist?
Unfortunately, no. The state still resets when you toggle the position. Click the heart, move the banner, and watch it reset.
Here's why: ngTemplateOutlet creates a new embedded view each time it attaches. You're reusing the template definition, but Angular still creates a fresh component instance every time. New view equals new component instance equals new state.
It's like having a blueprint for a house. Sure, you're using the same blueprint in both locations, but you're still building a new house from scratch each time.
Approach 2: CDK Template Portal
Angular CDK provides a Portal module with more sophisticated ways to move content. Let's try the Template Portal approach.
First, you need to install Angular CDK if you haven't already:
npm install @angular/cdk
Now let's refactor our component to use a Template Portal:
The complete, code-heavy deep dive (with diagrams, explanations, and tests) is published. Read the full article
Top comments (0)