File
Description
Fullscreen actions component
import {
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
Directive,
effect,
inject,
input,
output,
TemplateRef,
viewChild,
ViewContainerRef,
ViewRef,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { HraCommonModule } from '@hra-ui/common';
import { ButtonsModule } from '@hra-ui/design-system/buttons';
import { ExpansionPanelModule } from '@hra-ui/design-system/expansion-panel';
import { filter, MonoTypeOperatorFunction, pipe } from 'rxjs';
@Directive({
selector: '[hraViewOutlet]',
})
export class ViewOutletDirective {
readonly viewRef = input<ViewRef | undefined>(undefined, { alias: 'hraViewOutlet' });
private readonly viewContainerRef = inject(ViewContainerRef);
constructor() {
effect(() => this.attach());
}
attach(): void {
const viewRef = this.viewRef();
if (viewRef) {
this.viewContainerRef.insert(viewRef);
}
}
detach(): void {
const viewRef = this.viewRef();
const index = viewRef ? this.viewContainerRef.indexOf(viewRef) : -1;
if (index >= 0) {
this.viewContainerRef.detach(index);
}
}
}
@Component({
selector: 'hra-fullscreen-actions',
template: `<ng-content />`,
styles: `
:host {
display: flex;
width: 100%;
height: 100%;
flex-direction: row;
align-items: center;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FullscreenActionsComponent {}
@Component({
selector: 'hra-fullscreen-portal-content',
template: `<ng-content />`,
styles: `
:host {
width: 100%;
height: 100%;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FullscreenPortalContentComponent {}
@Component({
selector: 'hra-fullscreen-portal',
imports: [HraCommonModule, MatDialogModule, MatIconModule, ButtonsModule, ExpansionPanelModule, ViewOutletDirective],
templateUrl: './fullscreen-portal.component.html',
styleUrl: './fullscreen-portal.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FullscreenPortalComponent {
readonly tagline = input.required<string>();
readonly panelClass = input<string | string[]>();
readonly beforeOpened = output<void>();
readonly opened = output<void>();
readonly beforeClosed = output<void>();
readonly closed = output<void>();
readonly viewRef = computed(() => {
return this.viewContainerRef.createEmbeddedView(this.contentTemplateRef());
});
readonly rootNodes = computed(() => this.viewRef().rootNodes);
private readonly dialogService = inject(MatDialog);
private readonly viewContainerRef = inject(ViewContainerRef);
private readonly destroyRef = inject(DestroyRef);
private readonly viewOutlet = viewChild.required(ViewOutletDirective);
private readonly contentTemplateRef = viewChild.required<TemplateRef<void>>('contentTemplate');
private readonly dialogTemplateRef = viewChild.required<TemplateRef<void>>('dialogTemplate');
private dialogRef?: MatDialogRef<void>;
constructor() {
this.destroyRef.onDestroy(() => {
const dialogRef = this.dialogRef;
this.dialogRef = undefined;
dialogRef?.close();
this.viewRef().destroy();
});
}
open(): void {
if (this.dialogRef !== undefined) {
return;
}
const { dialogService, dialogTemplateRef } = this;
const panelClass = this.panelClass() ?? [];
const normalizedPanelClass = typeof panelClass === 'string' ? panelClass.split(' ') : panelClass;
this.beforeOpened.emit();
const dialogRef = (this.dialogRef = dialogService.open(dialogTemplateRef(), {
panelClass: [...normalizedPanelClass, 'fullscreen-panel'],
}));
dialogRef
.afterOpened()
.pipe(this.filterDialogEvents(dialogRef))
.subscribe(() => {
this.opened.emit();
});
dialogRef
.beforeClosed()
.pipe(this.filterDialogEvents(dialogRef))
.subscribe(() => {
this.beforeClosed.emit();
this.viewOutlet().attach();
});
dialogRef
.afterClosed()
.pipe(this.filterDialogEvents(dialogRef))
.subscribe(() => {
this.dialogRef = undefined;
this.closed.emit();
});
}
close(): void {
this.dialogRef?.close();
}
private filterDialogEvents<T>(dialogRef: MatDialogRef<void>): MonoTypeOperatorFunction<T> {
return pipe(
takeUntilDestroyed(this.destroyRef),
filter(() => this.dialogRef === dialogRef),
);
}
}
Legend
Html element with directive