Documentation Index
Fetch the complete documentation index at: https://mintlify.com/angular/components/llms.txt
Use this file to discover all available pages before exploring further.
The @angular/cdk/portal package provides a way to render dynamic content in a target location. Portals are used extensively by the Overlay system.
Installation
import {PortalModule} from '@angular/cdk/portal';
Types of Portals
ComponentPortal
Render a component dynamically:
import {Component, inject, ViewContainerRef} from '@angular/core';
import {ComponentPortal} from '@angular/cdk/portal';
@Component({selector: 'app-dynamic', template: '<p>Dynamic Component!</p>'})
export class DynamicComponent {}
@Component({
selector: 'app-portal-example',
template: `
<button (click)="loadComponent()">Load Component</button>
<ng-template [cdkPortalOutlet]="selectedPortal"></ng-template>
`,
})
export class PortalExample {
selectedPortal: ComponentPortal<DynamicComponent>;
loadComponent() {
this.selectedPortal = new ComponentPortal(DynamicComponent);
}
}
TemplatePortal
Render a template dynamically:
import {Component, TemplateRef, ViewChild, ViewContainerRef, inject} from '@angular/core';
import {TemplatePortal} from '@angular/cdk/portal';
@Component({
selector: 'app-template-portal',
template: `
<button (click)="showTemplate()">Show Template</button>
<ng-template #myTemplate let-data>
<div class="template-content">
<h3>{{ data.title }}</h3>
<p>{{ data.message }}</p>
</div>
</ng-template>
<div class="outlet">
<ng-template [cdkPortalOutlet]="selectedPortal"></ng-template>
</div>
`,
})
export class TemplatePortalExample {
@ViewChild('myTemplate') template: TemplateRef<any>;
private viewContainerRef = inject(ViewContainerRef);
selectedPortal: TemplatePortal<any>;
showTemplate() {
this.selectedPortal = new TemplatePortal(
this.template,
this.viewContainerRef,
{title: 'Hello', message: 'Template Portal'}
);
}
}
DomPortal
Move existing DOM elements:
import {Component, ElementRef, ViewChild} from '@angular/core';
import {DomPortal} from '@angular/cdk/portal';
@Component({
selector: 'app-dom-portal',
template: `
<div #content class="moveable">
This content can be moved!
</div>
<button (click)="moveContent()">Move Content</button>
<div class="target">
<ng-template [cdkPortalOutlet]="domPortal"></ng-template>
</div>
`,
})
export class DomPortalExample {
@ViewChild('content') content: ElementRef;
domPortal: DomPortal;
moveContent() {
this.domPortal = new DomPortal(this.content);
}
}
Portal Outlets
cdkPortalOutlet Directive
<ng-template [cdkPortalOutlet]="portal"></ng-template>
Programmatic Portal Attachment
import {Component, ViewChild} from '@angular/core';
import {CdkPortalOutlet, ComponentPortal} from '@angular/cdk/portal';
@Component({
selector: 'app-programmatic',
template: `<ng-template cdkPortalOutlet></ng-template>`,
})
export class ProgrammaticExample {
@ViewChild(CdkPortalOutlet) portalOutlet: CdkPortalOutlet;
ngAfterViewInit() {
const portal = new ComponentPortal(DynamicComponent);
const componentRef = this.portalOutlet.attach(portal);
// Access component instance
componentRef.instance.someProperty = 'value';
}
}
Advanced Examples
Portal with Injector
import {Component, inject, Injector} from '@angular/core';
import {ComponentPortal} from '@angular/cdk/portal';
@Component({
selector: 'app-injector-portal',
template: `<ng-template [cdkPortalOutlet]="portal"></ng-template>`,
})
export class InjectorPortalExample {
private injector = inject(Injector);
portal: ComponentPortal<DynamicComponent>;
createPortalWithDependencies() {
// Create custom injector
const customInjector = Injector.create({
parent: this.injector,
providers: [
{provide: 'CUSTOM_DATA', useValue: {id: 123}}
]
});
this.portal = new ComponentPortal(
DynamicComponent,
null, // viewContainerRef
customInjector
);
}
}
Conditional Portal Rendering
import {Component} from '@angular/core';
import {ComponentPortal, TemplatePortal} from '@angular/cdk/portal';
@Component({
selector: 'app-conditional-portal',
template: `
<button (click)="currentPortal = componentPortal">Show Component</button>
<button (click)="currentPortal = templatePortal">Show Template</button>
<button (click)="currentPortal = null">Clear</button>
<ng-template #myTemplate>
<p>Template content</p>
</ng-template>
<div class="portal-container">
<ng-template [cdkPortalOutlet]="currentPortal"></ng-template>
</div>
`,
})
export class ConditionalPortal {
componentPortal = new ComponentPortal(DynamicComponent);
templatePortal: TemplatePortal<any>;
currentPortal: ComponentPortal<any> | TemplatePortal<any> | null;
@ViewChild('myTemplate') template: TemplateRef<any>;
private viewContainerRef = inject(ViewContainerRef);
ngAfterViewInit() {
this.templatePortal = new TemplatePortal(this.template, this.viewContainerRef);
}
}
Portal with Context
import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {TemplatePortal} from '@angular/cdk/portal';
interface TemplateContext {
$implicit: string;
count: number;
}
@Component({
selector: 'app-context-portal',
template: `
<ng-template #contextTemplate let-item let-count="count">
<div>Item: {{ item }}, Count: {{ count }}</div>
</ng-template>
<ng-template [cdkPortalOutlet]="portal"></ng-template>
`,
})
export class ContextPortal {
@ViewChild('contextTemplate') template: TemplateRef<TemplateContext>;
private viewContainerRef = inject(ViewContainerRef);
portal: TemplatePortal<TemplateContext>;
ngAfterViewInit() {
this.portal = new TemplatePortal<TemplateContext>(
this.template,
this.viewContainerRef,
{
$implicit: 'Primary Value',
count: 42
}
);
}
}
API Reference
ComponentPortal
const portal = new ComponentPortal(
component: ComponentType<T>,
viewContainerRef?: ViewContainerRef | null,
injector?: Injector | null,
componentFactoryResolver?: ComponentFactoryResolver | null,
projectableNodes?: Node[][] | null
);
TemplatePortal
const portal = new TemplatePortal(
template: TemplateRef<C>,
viewContainerRef: ViewContainerRef,
context?: C
);
DomPortal
const portal = new DomPortal(element: ElementRef<HTMLElement> | HTMLElement);
Portal Methods
| Method | Returns | Description |
|---|
attach(host) | ComponentRef | EmbeddedViewRef | null | Attach portal to outlet |
detach() | void | Detach from outlet |
isAttached | boolean | Check if attached |
CdkPortalOutlet
Selector: [cdkPortalOutlet], [portalOutlet]
| Input | Type | Description |
|---|
cdkPortalOutlet | Portal<any> | Portal to attach |
| Method | Returns | Description |
|---|
attach(portal) | ComponentRef | EmbeddedViewRef | Attach portal |
detach() | void | Detach portal |
hasAttached() | boolean | Check if has portal |
Use Cases
Dynamic Tab Content
@Component({
selector: 'app-tabs',
template: `
<div class="tabs">
<button *ngFor="let tab of tabs" (click)="selectTab(tab)">
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<ng-template [cdkPortalOutlet]="selectedPortal"></ng-template>
</div>
`,
})
export class TabsComponent {
tabs = [
{label: 'Tab 1', component: Tab1Component},
{label: 'Tab 2', component: Tab2Component},
];
selectedPortal: ComponentPortal<any>;
selectTab(tab: any) {
this.selectedPortal = new ComponentPortal(tab.component);
}
}
Reusable Modal Service
import {Injectable, Injector} from '@angular/core';
import {Overlay, OverlayRef} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
@Injectable({providedIn: 'root'})
export class ModalService {
private overlay = inject(Overlay);
private injector = inject(Injector);
open<T>(component: ComponentType<T>, data?: any): OverlayRef {
const overlayRef = this.overlay.create({
hasBackdrop: true,
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),
});
const injector = Injector.create({
parent: this.injector,
providers: [{provide: 'MODAL_DATA', useValue: data}]
});
const portal = new ComponentPortal(component, null, injector);
overlayRef.attach(portal);
return overlayRef;
}
}
Best Practices
- Clean up - Detach portals when no longer needed
- Use typed contexts - For TemplatePortal type safety
- Provide injectors - For dependency injection in dynamic components
- Reuse portals - Detach/reattach instead of recreating
- Handle lifecycle - Components in portals have normal lifecycle
See Also