ViewChild returns 'undefined' - Angular2

ViewChild returns 'undefined' - Angular2

我正在尝试通过按父组件上的按钮来执行子组件的功能,但由于某种原因它未定义。

家长:

.. com1.html
  <ng-template #content let-c="close" let-d="dismiss">
    <div class="modal-header">
      <h4 class="modal-title">Title</h4>
    </div>
    <div class="modal-body" style="display: flex; justify-content: center;">
      <app-com2 [var1]="value" #traceRef></app-com2>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-outline-dark" (click)="savePNG()"><i class="fa fa-download"></i>PNG</button>
      <button type="button" class="btn btn-outline-dark" (click)="c()">Close</button>
    </div>
  </ng-template>

<button class="btn btn-sm btn-secondary" (click)="open(content)">Button</button>

...com1.ts
export class com1 implements OnInit {
@ViewChild('traceRef') traceRef;
value = "something";

  constructor(private modalService: NgbModal) {}

  savePNG() {
    this.traceRef.savePNG();
  }

  open(content: any) {
    this.modalService.open(content, { windowClass: 'temp-modal' });
  }
}

任何人都可以指出我正确的方向,因为其他线程没有帮助:( 即使使用 ngAfterViewInit,它仍然未定义。

我认为您需要以下内容。将 ChildComponent 替换为您的 child class 名称

@ViewChild('traceRef') 
      traceRef:ChildComponent

我注意到您的 parent 模板是模态模板,您可以在此处使用模板变量打开它:((click)="open(content)" 通过模态服务。所以这意味着它会动态添加到 DOM。

所以这可能是一个时间问题,因为 parent 是动态加载的。

我会尝试这个,在 CHILD 的 ngAfterViewInit 中,使用 EventEmitter 触发一个事件。抓取 parent 中的事件,然后将标志设置为 true。那么理论上你可以在 parent:

  savePNG() {
    if (this.childLoaded) this.traceRef.savePNG();
  }

您肯定知道 child 已加载,因此 savePNG 可用。但是,请参阅我的说明 - traceRef 仍将未定义。

因此您需要在 EventEmitter 中传递对 child 函数的引用,并使用它代替 #traceRef。

例如 - 在 child:

export class app-com2 implements blah blah {
  @Output() childLoaded: EventEmitter<any> = new EventEmitter<any>();

  savePNG() {
    // code to save the image
  }

  ngAfterViewInit() {
    this.childLoaded.emit(this.savePNG);
  }
}

Parent:

..com1.html

  <ng-template #content let-c="close" let-d="dismiss">
    <div class="modal-header">
      <h4 class="modal-title">Title</h4>
    </div>
    <div class="modal-body" style="display: flex; justify-content: center;">
      <app-com2 [var1]="value" (childLoaded)="handleChildLoaded($event)"></app-com2>
    </div>

..com1.ts

handleChildLoaded(save: any) {
  const result = save;
  console.log(result);
}

Docs 在 child 到 parent 通讯上。

注意

再考虑一下 - 这是一个 lifecycle/timing 问题,因此您的代码将永远无法运行。这可能是为什么:

  1. 您的 parent 组件加载,触发所有生命周期挂钩(例如 ngOnInit 和 ngAfterViewInit 等)。 #content 得到处理并在 ngAfterViewInit 和更高版本的挂钩中可用。

  2. 您现在单击按钮加载#content(可用)。但它有 <app-com2> 组件,它由 #traceRef 模板变量引用。但是模板变量仅在 ngAfterViewInit 钩子触发之前处理 - 并且已经为 parent 触发,因此 #traceRef 到达太晚 Angular 无法连接它。因此它将永远是未定义的。

请参阅下面关于 (2) 的评论 - 不太正确,但我怀疑这是因为您加载模态的方式导致 #traceRef 始终未定义。

它无法像在 ng-template 的常见场景中那样工作的原因是 NgbModal 服务创建模板并且没有将其附加到父视图而是附加到根 ApplicationRef

const viewRef = content.createEmbeddedView(context);
this._applicationRef.attachView(viewRef);

https://github.com/ng-bootstrap/ng-bootstrap/blob/0f8055f54dad84e235810ff7bfc846911c168b7a/src/modal/modal-stack.ts#L107-L109

这意味着您的查询对于您的组件将始终是干净的。

在一般情况下(见@ConnorsFan提供的example)我们使用vcRef.createEmbeddedView负责在父视图中检查查询:

vcRef.createEmbeddedView
          ||
          \/
function attachEmbeddedView(
   ...
  Services.dirtyParentQueries(view);  <========== set dirty for queries

https://github.com/angular/angular/blob/0ebdb3d12f8cab7f9a24a414885ae3c646201e90/packages/core/src/view/view_attach.ts#L12-L23

我在这里看到的简单解决方案是直接在模板中传递或调用引用:

(click)="traceRef.savePNG()"