I'm working on an Angular project where I'm using Angular CDK's Overlay and TemplatePortal to display a dynamic menu. The menu should emit events when an option is selected, but the EventEmitter doesn't seem to work as expected.
Here's what I'm doing:
Here's the relevant code:
import {
Component,
ViewChild,
TemplateRef,
ViewContainerRef,
Output,
EventEmitter,
} from '@angular/core';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
@Component({
selector: 'app-menu-example',
templateUrl: './menu-example.component.html',
styleUrls: ['./menu-example.component.css'],
})
export class MenuExampleComponent {
@ViewChild('menuTemplate', { read: TemplateRef }) menuTemplate!: TemplateRef<any>;
@Output() triggerOption = new EventEmitter<string>();
private overlayRef!: OverlayRef;
constructor(private overlay: Overlay, private viewContainerRef: ViewContainerRef) {}
openMenu(event: MouseEvent, control: any) {
if (this.overlayRef) {
this.overlayRef.dispose();
}
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(event.target as HTMLElement)
.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
offsetX: 0,
offsetY: 8,
}]);
this.overlayRef = this.overlay.create({
positionStrategy,
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop',
panelClass: 'custom-panel-class',
});
const portal = new TemplatePortal(
this.menuTemplate,
this.viewContainerRef,
{ $implicit: control, component: this }
);
this.overlayRef.attach(portal);
this.overlayRef.backdropClick().subscribe(() => this.closeMenu());
}
closeMenu() {
if (this.overlayRef) {
this.overlayRef.dispose();
}
}
openOptionsView(control: any) {
console.log('Settings clicked for:', control);
this.triggerOption.emit('Settings');
}
removeControl(control: any) {
console.log('Remove clicked for:', control);
this.triggerOption.emit('Remove');
}
}
<ng-template #menuTemplate let-control let-component="component">
<div class="control-menu-bar">
<button
class="control-menu-bar-item"
matTooltip="Settings"
(click)="component.openOptionsView(control)"
>
<mat-icon>settings_suggest</mat-icon>
</button>
<button
class="control-menu-bar-item"
matTooltip="Remove"
(click)="component.removeControl(control)"
>
<mat-icon>delete_forever</mat-icon>
</button>
</div>
</ng-template>
<button (click)="openMenu($event, { name: 'Control 1' })">Open Menu</button>
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css'],
})
export class ParentComponent {
handleMenuOption(option: string) {
console.log('Menu option selected:', option);
}
}
<app-menu-example (triggerOption)="handleMenuOption($event)"></app-menu-example>
Question:
Any insights or suggestions would be greatly appreciated! Thank you.
The implicit prevented the component template reference variable for being initialized property, instead create two variables.
const portal = new TemplatePortal(
this.menuTemplate,
this.viewContainerRef,
{ control, component: this }
);
If you want to implement using $implicit
you can pass them as properties of a single object.
const portal = new TemplatePortal(
this.menuTemplate,
this.viewContainerRef,
{ $implicit: { control, component: this } }
);
HTML:
<ng-template #menuTemplate let-item>
<div class="control-menu-bar">
<button
class="control-menu-bar-item"
matTooltip="Settings"
(click)="item.component.openOptionsView(item.control)"
>
<mat-icon>settings_suggest</mat-icon>
</button>
<button
class="control-menu-bar-item"
matTooltip="Remove"
(click)="item.component.removeControl(item.control)"
>
<mat-icon>delete_forever</mat-icon>
</button>
</div>
</ng-template>
<button (click)="openMenu($event, { name: 'Control 1' })">Open Menu</button>