I am working on my library Contexr. I just refactored my application to use the Angular CDK Overlay to display the context menu, so I don't have to include some component in the actual application anymore (one less installation-step).
I once used the FlexibleConnectedPositionStrategy to create a dropdown beneath an element, which would keep within the page. This position strategy is created like using an ElementRef:
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(elementRef)
.left(state.left + 'px')
.top(state.top + 'px');
The problem is that I don't have an ElementRef to go from. My overlay should be flexible connected to my .left() and .top(). Is there a way to do this with the FlexibleConnectedPositionStrategy? Currently I'm trying to use the GlobalPositionStrategy, but that doesn't account for elements going off-screen.
The class that opens the overlay:
@Injectable({
providedIn: 'root'
})
export class ContextMenuService {
private overlayRef: OverlayRef;
constructor(private overlay: Overlay, private injector: Injector) {}
public open(state: ContextState) {
const overlayConfig = this.getOverlayConfig(state);
this.overlayRef = this.overlay.create(overlayConfig);
const contextMenuRef = new ContextMenuOverlayRef(this.overlayRef);
this.attachDialogContainer(this.overlayRef, state, contextMenuRef);
}
private getOverlayConfig(state: ContextState) {
const positionStrategy = this.overlay.position()
.global()
.left(state.left + 'px')
.top(state.top + 'px');
return {
positionStrategy: positionStrategy
};
}
private createInjector(state: ContextState, dialogRef: ContextMenuOverlayRef) {
const injectionTokens = new WeakMap();
injectionTokens.set(ContextMenuOverlayRef, dialogRef);
injectionTokens.set(CONTEXT_MENU_OVERLAY_DATA, state);
return new PortalInjector(this.injector, injectionTokens);
}
private attachDialogContainer(overlayRef: OverlayRef, state: ContextState, contextMenuOverlayRef: ContextMenuOverlayRef) {
const injector = this.createInjector(state, contextMenuOverlayRef);
const containerPortal = new ComponentPortal(ContextMenuComponent, null, injector);
overlayRef.attach(containerPortal);
}
public close() {
if (this.overlayRef) {
this.overlayRef.dispose();
}
}
}
Turns out you can use the FlexibleConnectedPositionStrategy after all. I found some context menu library on Github, called ngrx-rightclick (thank you!). Here they created a new ElementRef based on the click event.
private getOverlayConfig(event: MouseEvent, state: ContextState) {
const target = {
getBoundingClientRect: (): ClientRect => ({
bottom: event.clientY,
height: 0,
left: event.clientX,
right: event.clientX,
top: event.clientY,
width: 0,
}),
};
const element = new ElementRef(target);
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(element)
.withFlexibleDimensions(false)
.withPositions([
{
originX: 'end',
originY: 'top',
overlayX: 'start',
overlayY: 'top',
},
{
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'top',
},
{
originX: 'end',
originY: 'bottom',
overlayX: 'start',
overlayY: 'bottom',
},
{
originX: 'start',
originY: 'bottom',
overlayX: 'end',
overlayY: 'bottom',
},
]);
return {
positionStrategy: positionStrategy
};
}