Angular - Can a child component reference a parent's template variable?

痞子三分冷 提交于 2019-12-07 18:40:10

问题


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.


回答1:


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.




回答2:


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;
  }
}


来源:https://stackoverflow.com/questions/45745607/angular-can-a-child-component-reference-a-parents-template-variable

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!