Portal и Overlay - мощные инструменты Angular CDK для динамического отображения контента в произвольных местах DOM. Рассмотрим их применение подробно.
Portal - абстракция для динамического контента, который можно "прикрепить" к любой точке приложения. Бывает трех типов:
Overlay - сервис для управления позиционированием поверх основного контента (модалки, попапы, меню)
Сначала импортируем необходимые модули:
import { OverlayModule, PortalModule } from '@angular/cdk/overlay';
@NgModule({
imports: [
OverlayModule,
PortalModule
]
})
export class AppModule { }
import { ComponentPortal } from '@angular/cdk/portal';
// Создаем портал для компонента
const componentPortal = new ComponentPortal(MyDynamicComponent);
// Где-то в коде компонента
@ViewChild('portalOutlet') portalOutlet: PortalOutlet;
attachComponent() {
this.portalOutlet.attach(componentPortal);
}
<ng-template #templatePortal>
<div>Динамический контент</div>
</ng-template>
import { TemplatePortal } from '@angular/cdk/portal';
@ViewChild('templatePortal') template: TemplateRef<any>;
ngAfterViewInit() {
this.templatePortal = new TemplatePortal(
this.template,
this.viewContainerRef
);
}
import { Overlay } from '@angular/cdk/overlay';
constructor(private overlay: Overlay) {}
openOverlay() {
// Создаем Overlay конфигурацию
const overlayRef = this.overlay.create({
hasBackdrop: true,
positionStrategy: this.overlay.position()
.global()
.centerHorizontally()
.centerVertically(),
scrollStrategy: this.overlay.scrollStrategies.block(),
width: '400px',
height: '300px'
});
// Прикрепляем компонент
const portal = new ComponentPortal(MyDialogComponent);
overlayRef.attach(portal);
// Закрытие по клику на бэкдроп
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
}
// Относительно элемента
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(this.triggerElement.nativeElement)
.withPositions([
{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
offsetY: 8
}
]);
// Глобальное позиционирование
const globalPosition = this.overlay.position()
.global()
.top('20px')
.right('20px');
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
@Directive({
selector: '[appDropdown]'
})
export class DropdownDirective {
private overlayRef: OverlayRef;
constructor(
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private elementRef: ElementRef
) {}
@Input() set appDropdown(menu: TemplateRef<any>) {
this.menuTemplate = menu;
}
@HostListener('click') onClick() {
if (this.overlayRef) {
this.close();
} else {
this.open();
}
}
open() {
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(this.elementRef)
.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
offsetY: 8
}]);
this.overlayRef = this.overlay.create({
positionStrategy,
scrollStrategy: this.overlay.scrollStrategies.reposition(),
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop'
});
const portal = new TemplatePortal(
this.menuTemplate,
this.viewContainerRef
);
this.overlayRef.attach(portal);
this.overlayRef.backdropClick().subscribe(() => {
this.close();
});
}
close() {
this.overlayRef.dispose();
this.overlayRef = null;
}
}
// Создаем инжектор с данными
const injector = Injector.create({
providers: [
{ provide: DIALOG_DATA, useValue: { title: 'Hello', content: 'World' } }
],
parent: this.injector
});
// Прикрепляем портал с кастомным инжектором
overlayRef.attach(new ComponentPortal(MyDialogComponent, null, injector));
// В компоненте диалога
constructor(@Inject(DIALOG_DATA) public data: any) {}
overlayRef.overlayElement.classList.add('fade-in');
.fade-in {
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
overlayRef.updateSize({
width: '80vw',
height: '80vh'
});
overlayRef.updatePositionStrategy(
this.overlay.position().global().centerHorizontally().centerVertically()
);
dispose()
для OverlayRefПример обработки ESC:
import { ESCAPE } from '@angular/cdk/keycodes';
overlayRef.keydownEvents().subscribe(event => {
if (event.keyCode === ESCAPE) {
this.close();
}
});
Portal и Overlay из Angular CDK предоставляют мощный API для создания динамических, доступных и производительных оверлейных компонентов. Их комбинация позволяет реализовать сложные UI-паттерны, сохраняя контроль над позиционированием, управлением состоянием и производительностью.