The Angular CDK Portal module provides a system for dynamically rendering content in different locations in the DOM. Portals allow you to render a component or template in a "host" location that may be outside the normal component tree.
The foundational classes for creating portals.
/**
* Base class for all portals that can be attached to a PortalOutlet
* @template T The type of content the portal will render
*/
abstract class Portal<T> {
/**
* Attach this portal to a host outlet
* @param host - The outlet to attach to
* @returns The result of attaching the portal
*/
attach(host: PortalOutlet): T;
/**
* Detach this portal from its host
*/
detach(): void;
/**
* Whether this portal is currently attached
*/
isAttached: boolean;
/**
* Set the attached host reference
* @param host - The host outlet
*/
setAttachedHost(host: PortalOutlet | null): void;
}
/**
* Interface for portal outlets that can host portals
*/
interface PortalOutlet {
/**
* Attach a portal to this outlet
* @param portal - The portal to attach
* @returns The result of attaching the portal
*/
attach(portal: Portal<any>): any;
/**
* Detach the currently attached portal
* @returns The result of detaching the portal
*/
detach(): any;
/**
* Dispose of the outlet and clean up resources
*/
dispose(): void;
/**
* Whether this outlet has an attached portal
* @returns True if a portal is attached
*/
hasAttached(): boolean;
}Portal implementation for Angular components.
/**
* Portal for rendering Angular components
* @template T The component type
*/
class ComponentPortal<T> extends Portal<ComponentRef<T>> {
/**
* The component to render
*/
component: ComponentType<T>;
/**
* The ViewContainerRef to render the component in
*/
viewContainerRef?: ViewContainerRef;
/**
* The injector to use for the component
*/
injector?: Injector;
/**
* The ComponentFactoryResolver to use (deprecated in Angular 13+)
*/
componentFactoryResolver?: ComponentFactoryResolver;
/**
* Create a component portal
* @param component - The component class to render
* @param viewContainerRef - Optional ViewContainerRef
* @param injector - Optional injector
* @param componentFactoryResolver - Optional component factory resolver (deprecated)
*/
constructor(
component: ComponentType<T>,
viewContainerRef?: ViewContainerRef,
injector?: Injector,
componentFactoryResolver?: ComponentFactoryResolver
);
}Usage Example:
import { ComponentPortal } from '@angular/cdk/portal';
// Create a portal for a component
const portal = new ComponentPortal(MyComponent);
// Attach to an outlet
const componentRef = outlet.attach(portal);
// Access the component instance
componentRef.instance.someProperty = 'value';Portal implementation for Angular templates.
/**
* Portal for rendering Angular templates
* @template C The template context type
*/
class TemplatePortal<C = any> extends Portal<EmbeddedViewRef<C>> {
/**
* The template to render
*/
templateRef: TemplateRef<C>;
/**
* The ViewContainerRef to render the template in
*/
viewContainerRef: ViewContainerRef;
/**
* The context object for the template
*/
context?: C;
/**
* Create a template portal
* @param templateRef - The template to render
* @param viewContainerRef - The ViewContainerRef to render in
* @param context - Optional context for the template
*/
constructor(
templateRef: TemplateRef<C>,
viewContainerRef: ViewContainerRef,
context?: C
);
}Usage Example:
import { TemplatePortal } from '@angular/cdk/portal';
@Component({
template: `
<ng-template #myTemplate let-message="message">
<p>{{ message }}</p>
</ng-template>
`
})
export class MyComponent {
@ViewChild('myTemplate') template!: TemplateRef<any>;
constructor(private viewContainerRef: ViewContainerRef) {}
createPortal() {
const portal = new TemplatePortal(
this.template,
this.viewContainerRef,
{ message: 'Hello from template portal!' }
);
return portal;
}
}Portal implementation for raw DOM elements.
/**
* Portal for rendering DOM elements
*/
class DomPortal extends Portal<HTMLElement> {
/**
* The DOM element to render
*/
element: Element;
/**
* Create a DOM portal
* @param element - The DOM element to render
*/
constructor(element: Element);
}Implementations of PortalOutlet for different scenarios.
/**
* DOM-based portal outlet for rendering portals to DOM elements
*/
class DomPortalOutlet implements PortalOutlet {
/**
* The DOM element that will host the portal content
*/
outletElement: Element;
/**
* Create a DOM portal outlet
* @param outletElement - The element to host portal content
* @param componentFactoryResolver - Optional component factory resolver (deprecated)
* @param appRef - Optional application reference
* @param injector - Optional injector
* @param document - Optional document reference
*/
constructor(
outletElement: Element,
componentFactoryResolver?: ComponentFactoryResolver,
appRef?: ApplicationRef,
injector?: Injector,
document?: Document
);
/**
* Attach a portal to this outlet
* @param portal - The portal to attach
* @returns The result of attaching the portal
*/
attach<T>(portal: Portal<T>): T;
/**
* Detach the currently attached portal
* @returns The detached content
*/
detach(): void;
/**
* Dispose of the outlet and clean up resources
*/
dispose(): void;
/**
* Whether this outlet has an attached portal
* @returns True if a portal is attached
*/
hasAttached(): boolean;
}
/**
* Deprecated alias for DomPortalOutlet
* @deprecated Use DomPortalOutlet instead
*/
class DomPortalHost extends DomPortalOutlet {}Directives for using portals declaratively in templates.
/**
* Directive to create a template portal from a template
*/
@Directive({
selector: '[cdkPortal]'
})
class CdkPortal extends TemplatePortal {
/**
* The template reference for this portal
*/
templateRef: TemplateRef<any>;
constructor(
templateRef: TemplateRef<any>,
viewContainerRef: ViewContainerRef
);
}
/**
* Directive to mark an element as a portal outlet
*/
@Directive({
selector: '[cdkPortalOutlet]'
})
class CdkPortalOutlet implements OnInit, OnDestroy, PortalOutlet {
/**
* The portal to attach to this outlet
*/
@Input() cdkPortalOutlet: Portal<any> | null;
/**
* Deprecated alias for cdkPortalOutlet
* @deprecated Use cdkPortalOutlet instead
*/
@Input() portal: Portal<any> | null;
/**
* Manually attach a portal to this outlet
* @param portal - The portal to attach
*/
attach(portal: Portal<any>): void;
/**
* Detach the currently attached portal
*/
detach(): void;
/**
* Whether this outlet has an attached portal
* @returns True if a portal is attached
*/
hasAttached(): boolean;
}Usage Example:
<!-- Create a template portal -->
<ng-template cdkPortal #myPortal>
<p>This content can be rendered anywhere!</p>
</ng-template>
<!-- Create a portal outlet -->
<div cdkPortalOutlet></div>
<!-- Programmatically attach the portal -->
<button (click)="attachPortal()">Attach Portal</button>@Component({
selector: 'app-portal-example',
template: /* template above */
})
export class PortalExampleComponent {
@ViewChild('myPortal') portal!: CdkPortal;
@ViewChild(CdkPortalOutlet) outlet!: CdkPortalOutlet;
attachPortal() {
this.outlet.attach(this.portal);
}
}Type definition for component constructors.
/**
* Generic component type interface
* @template T The component instance type
*/
interface ComponentType<T> {
new (...args: any[]): T;
}/**
* Angular module that includes all portal functionality
*/
@NgModule({
declarations: [
CdkPortal,
CdkPortalOutlet
],
exports: [
CdkPortal,
CdkPortalOutlet
]
})
class PortalModule {}import { ComponentPortal, DomPortalOutlet } from '@angular/cdk/portal';
import { ApplicationRef, ComponentFactoryResolver, Injector } from '@angular/core';
@Injectable()
export class DynamicComponentService {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
private injector: Injector
) {}
createComponent<T>(
component: ComponentType<T>,
hostElement: Element,
data?: Partial<T>
): ComponentRef<T> {
// Create portal and outlet
const portal = new ComponentPortal(component);
const outlet = new DomPortalOutlet(
hostElement,
this.componentFactoryResolver,
this.appRef,
this.injector
);
// Attach portal to outlet
const componentRef = outlet.attach(portal);
// Set component properties
if (data) {
Object.assign(componentRef.instance, data);
}
// Trigger change detection
componentRef.changeDetectorRef.detectChanges();
return componentRef;
}
}@Component({
selector: 'app-modal',
template: `
<div class="modal-backdrop">
<div class="modal-content">
<ng-container cdkPortalOutlet></ng-container>
</div>
</div>
`
})
export class ModalComponent implements OnInit {
@ViewChild(CdkPortalOutlet) portalOutlet!: CdkPortalOutlet;
@Input() contentPortal!: Portal<any>;
ngOnInit() {
if (this.contentPortal) {
this.portalOutlet.attach(this.contentPortal);
}
}
}
// Usage
@Component({
template: `
<ng-template cdkPortal #modalContent>
<h2>Modal Title</h2>
<p>Modal content goes here</p>
<button (click)="closeModal()">Close</button>
</ng-template>
<button (click)="openModal()">Open Modal</button>
<app-modal
*ngIf="showModal"
[contentPortal]="modalContent">
</app-modal>
`
})
export class AppComponent {
@ViewChild('modalContent') modalContent!: CdkPortal;
showModal = false;
openModal() {
this.showModal = true;
}
closeModal() {
this.showModal = false;
}
}import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
@Injectable()
export class OverlayService {
constructor(private overlay: Overlay) {}
openComponent<T>(
component: ComponentType<T>,
config?: {
data?: Partial<T>;
hasBackdrop?: boolean;
backdropClass?: string;
}
): OverlayRef {
const overlayRef = this.overlay.create({
hasBackdrop: config?.hasBackdrop ?? true,
backdropClass: config?.backdropClass,
positionStrategy: this.overlay.position()
.global()
.centerHorizontally()
.centerVertically()
});
const portal = new ComponentPortal(component);
const componentRef = overlayRef.attach(portal);
if (config?.data) {
Object.assign(componentRef.instance, config.data);
}
return overlayRef;
}
openTemplate<C>(
templateRef: TemplateRef<C>,
viewContainerRef: ViewContainerRef,
context?: C
): OverlayRef {
const overlayRef = this.overlay.create({
hasBackdrop: true,
positionStrategy: this.overlay.position()
.global()
.centerHorizontally()
.centerVertically()
});
const portal = new TemplatePortal(templateRef, viewContainerRef, context);
overlayRef.attach(portal);
return overlayRef;
}
}