or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdaccordion.mdcollections-data.mddialogs.mddrag-drop.mdindex.mdlistbox.mdmenus.mdobservers.mdoverlays.mdplatform-utilities.mdportals.mdscrolling.mdtesting.mdtext-fields.md
tile.json

portals.mddocs/

Portals

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.

Capabilities

Portal Base Classes

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;
}

Component Portal

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';

Template Portal

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;
  }
}

DOM 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);
}

Portal Outlets

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 {}

Portal Directives

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);
  }
}

Component Type Interface

Type definition for component constructors.

/**
 * Generic component type interface
 * @template T The component instance type
 */
interface ComponentType<T> {
  new (...args: any[]): T;
}

Module

/**
 * Angular module that includes all portal functionality
 */
@NgModule({
  declarations: [
    CdkPortal,
    CdkPortalOutlet
  ],
  exports: [
    CdkPortal,
    CdkPortalOutlet
  ]
})
class PortalModule {}

Usage Patterns

Dynamic Component Rendering

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;
  }
}

Template-based Content Projection

@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;
  }
}

Creating a Reusable Overlay Service

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;
  }
}