I am working on an angular 4 application using a primefaces primeng component, p-contextmenu
. I am trying to tell a child element to use a template variable of a parent component.
app.html:
<div>
<router-outlet></router-outlet>
<div #contextMenuHolder></div>
</div>
mycomponent.html:
<p-contextMenu [appendTo]="contextMenuHolder" [model]="items"></p-contextMenu>
Obviously it fails as the contextMenuHolder
does not exist in the child, but in its parent:
Angular: Identifier 'contextMenuHolder' is not defined. The component declaration, template variable declarations, and element references do not contain such a member
Can you reference a parent's template variable from a child component?
Edit:
Plunkr with it broken. This plunkr shows it not working, but no error messages.
The documentation for appendTo
says
Target element to attach the overlay, valid values are "body" or a local ng-template variable of another element.
Maybe a service can resolve the issue :
@Injectable()
export class ContextMenuHolder {
contextMenu: any; // TODO set a type (HTMLElement?)
getContextMenu() {
return this.contextMenu;
}
setContextMenu(contextMenu: any) {
this.contextMenu = contextMenu;
}
}
In your app.ts
, you inject the service and set the value.
In your component.ts
, you inject the service and get the value.
I did not tested it but it should work. If the contextMenu
can change, you will have to use event listeners or observable.
Thanks to Ludovic Guillaume I was able to find a solution:
https://plnkr.co/edit/kwnkSKDPFs1Bp2xOHqIu
child.ts:
import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {ContextMenuHolderService} from './context-menu-holder.service'
@Component({
selector: 'child-comp',
template: `
<div>
<h2>Hello child</h2>
<span #mySpan>this bit has a context menu</span> <br>
<span #parentSpan>this bit is the target for the parent span.</span>
<p-contextMenu [target]="mySpan" [appendTo]="parentContext" [model]="items"></p-contextMenu>
<p-contextMenu [target]="parentSpan" [appendTo]="parentContext" [model]="itemsForParent"></p-contextMenu>
</div>
`,
})
export class ChildComponent {
private items: MenuItem[];
parentContext: any;
constructor(private cmhs : ContextMenuHolderService) {
}
ngOnInit() {
this.items = [{ label: 'mySpans context menu' }];
this.itemsForParent = [{ label: 'parent context menu items' }];
console.log('child init', this.cmhs.getContextMenuParent())
this.parentContext = this.cmhs.getContextMenuParent().nativeElement;
}
}
Here, the child component has built the context menu with the items it wants in the menu. This menu needs to reside in the parent (sometimes this is necessary for styling or positioning reasons). The child has a parentContext
object that will be set during it's onInit
phase of the lifecycle.
parent (app.ts):
//our root app component
import {Component, NgModule, VERSION, ViewChild} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {ChildComponent} from './child'
import {ContextMenuModule,MenuItem} from 'primeng/primeng'
import {ContextMenuHolderService} from './context-menu-holder.service'
@Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<div #parentContextTarget>This is in the parent component and should have a context menu</div>
<div #parentContextWrapper></div>
<child-comp></child-comp>
</div>
`,
})
export class App {
name:string;
@ViewChild('parentContextWrapper') parentContextWrapper;
constructor(private cmhs : ContextMenuHolderService) {
this.name = `Angular! v${VERSION.full}`
// console.log('parent constructor')
}
ngOnInit(){
console.log('parent init - parent context wrapper', this.parentContextWrapper)
this.cmhs.setContextMenuParent(this.parentContextWrapper)
}
}
The parent sets the object in the service during it's onInit
stage. Initially I thought this had to be during the afterViewInit
, but this ended up being too late in the lifecycle.
service:
import {Injectable} from '@angular/core';
@Injectable()
export class ContextMenuHolderService {
contextMenuParent: any; // TODO set a type (HTMLElement?)
getContextMenuParent() {
console.log('returning cmp', this.contextMenuParent)
return this.contextMenuParent;
}
setContextMenuParent(contextMenuParent: any) {
console.log('settin context menu parent', contextMenuParent)
this.contextMenuParent = contextMenuParent;
}
}