ViewChild returns 'undefined' - Angular2

不打扰是莪最后的温柔 提交于 2020-01-24 22:47:12

问题


I'm trying to execute a function of a child component with a push of a button on the parent but for some reason its undefined.

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" #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' });
  }
}

Could anyone point me in the right directions cuz the other threads were of no help :( Even with ngAfterViewInit its still undefined.


回答1:


The reason why it is not working as it would be in common scenario with ng-template is that NgbModal service creates template and doesn't attach it to parent view but rather to root 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

This means your query will be always undirty for your component.

In common cases(see example provided by @ConnorsFan) we use vcRef.createEmbeddedView which is responsible for checking queries in parent view:

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

The simple solution i can see here is just pass or call reference directly in template:

(click)="traceRef.savePNG()"



回答2:


I think you need something as below. replace ChildComponent with your child class name

@ViewChild('traceRef') 
      traceRef:ChildComponent



回答3:


I notice that your parent template is a modal, which you open using a template variable here: ((click)="open(content)" via your modal service. So this means it gets added to the DOM dynamically.

So this could be a timing issue because the parent is loaded dynamically.

I would try this, in the ngAfterViewInit of the CHILD, fire an event using EventEmitter. Grab the event in the parent, and then set a flag to true. Then IN THEORY you can do this in the parent:

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

You know for sure the child is loaded and so savePNG is available. However, see my note - traceRef will still be undefined.

So you need to pass a reference to the child function in the EventEmitter and use that instead of #traceRef.

Eg - in the 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 on child to parent comms.

NOTE

Thinking about this more - this is a lifecycle/timing issue, so your code will never work. This might be why:

  1. Your parent component loads, fires all lifecycle hooks (eg. ngOnInit and ngAfterViewInit etc). #content gets processed and becomes available in ngAfterViewInit and later hooks.

  2. You now click a button to load #content (which is available). But it has <app-com2> component, which is referenced with the #traceRef template variable. But template variables are only processed just before the ngAfterViewInit hook fires - and that has already fired for the parent, and so #traceRef arrives too late for Angular to hook it up. Hence it will always be undefined.

See comments below regarding (2) - not quite right, but I suspect it is because of the way you are loading the modal that #traceRef will always be undefined.



来源:https://stackoverflow.com/questions/50353977/viewchild-returns-undefined-angular2

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