Angular CDK : attach overlay to a clicked element
Solution 1:
There're a two amazing articles about using OverLay from CDK in Netanet Basal's Blog
- Creating Powerful Components with Angular CDK
- Context Menus Made Easy with Angular CDK
I try to simplyfied in this stackblitz
Basicaly you has a service that inject Overlay
constructor(private overlay: Overlay) { }
To open a template you pass the origin (I called him "origin"), the template (I called menu) and the viewContainerRef of your component
this.overlayRef = this.overlay.create(
this.getOverlayConfig({ origin: origin})
);
//I can pass "data" as implicit and "close" to close the menu
this.overlayRef.attach(new TemplatePortal(menu, viewContainerRef, {
$implicit: data, close:this.close
}));
getOverLayConfig return a config some like
private getOverlayConfig({ origin}): OverlayConfig {
return new OverlayConfig({
hasBackdrop: false,
backdropClass: "popover-backdrop",
positionStrategy: this.getOverlayPosition(origin),
scrollStrategy: this.overlay.scrollStrategies.close()
});
}
And the position strategie is where you want to attach the template -an array with your preferered positions, e.g.
[
{
originX: "center",
originY: "bottom",
overlayX: "center",
overlayY: "top"
},
]
Well, the other part of the code is about close the template element. I choose create in the service a function open that
1.-attach the element
2.-create a subscription of
this.sub = fromEvent<MouseEvent>(document, "click")
3.-return an observable that return null or the argument you pass in a function "close"(*)
NOTE: Don't forget incluyed in your css ~@angular/cdk/overlay-prebuilt.css
(*) this allow me my template like
<ng-template #tpl let-close="close" let-data>
<div class="popover" >
<h5>{{name}} {{data.data}}</h5> //<--name is a variable of component
//data.data a variable you can pass
And here's some amazing content. It's very engaging. Right?
<div>
<a (click)="close('uno')">Close</a> //<--this close and return 'uno'
</div>
</div>
</ng-template>
Update if we want to attach a component first we need remember that must be in the entryComponents of the module
@NgModule({
imports: [ BrowserModule, FormsModule,OverlayModule ],
declarations: [ AppComponent,HelloComponent], //<--HERE
bootstrap: [ AppComponent ],
entryComponents:[HelloComponent] //<--and HERE
})
Well, to attach a component is simple change the attach and use ComponentPortal, e.g.
const ref=this.overlayRef.attach(new ComponentPortal(HelloComponent,viewContainerRef))
then, if our component has somes inputs, e.g.
@Input() name="Angular";
@Input() obj={count:0};
We can use ref.instance to access to the component, e.g
ref.instance.name="New Name"
But as we want maintain the service the most general use, I want to use the argument "data" to give values to the variables, so our function "open" becomes
open(origin: any, component: any, viewContainerRef: ViewContainerRef, data: any) {
this.close(null);
this.overlayRef = this.overlay.create(
this.getOverlayConfig({ origin: origin})
);
const ref=this.overlayRef.attach(new ComponentPortal(component, viewContainerRef));
for (let key in data) //here pass all the data to our component
{
ref.instance[key]=data[key]
}
...rest of code...
}
As always, if we pass an object, all the changes in the component change the properties of the object, so in our main component can be make some like
obj={count:2}
open(origin:any,menu:any,index:number)
{
this.popupService.open(origin,HelloComponent,this.viewContainerRef,
{name:'new Name'+index,obj:this.obj})
.subscribe(res=>{
console.log(res)
})
}
See that, as I pass as obj an object any change in the component change the propertie of the object, in my case the component is very simple
@Component({
selector: 'component',
template:`Hello {{name}}
<button (click)="obj.count=obj.count+1">click</button>
`
})
export class HelloComponent {
@Input() name="Angular";
@Input() obj={count:0};
}
You can see in a new stackblitz
Update2 To close the panel from the HelloComponent we need inject the service as public and use close. more or less, a button
<button (click)="popupService.close(4)">close</button>
where you inject the service
constructor(public popupService: MenuContextualService){}