2

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.

8
  • Does it work if you directly try to access the component without the templateRef? Like @ViewChild(AppComComponent) traceRef; Commented May 15, 2018 at 15:35
  • What is undefined? this.traceRef or savePNG? Commented May 15, 2018 at 15:35
  • 1
    this.traceRef is undefined Commented May 15, 2018 at 15:37
  • You said even with "ngAfterInit" - but it must be used in "ngAfterViewInit". The template variable will not be available to your component until after the view has initialised in that lifecycle hook. Docs: angular.io/api/core/ViewChild Commented May 15, 2018 at 15:41
  • 2
    Please post your entire template and class to not miss anything :) Commented May 15, 2018 at 15:44

3 Answers 3

5

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()"
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks allot ! I was trying to fix this problem the whole day :D
0

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

@ViewChild('traceRef') 
      traceRef:ChildComponent

4 Comments

I can't initialize like that, I have to pass on a value and I don't want to change it. Just want to emit some kind of a trigger with a push of a button to activate a function in the child component.
yes. You can ignore that initialization code. That is not required.
Still undefined :(
Did you change ChildComponent in my sample code to your class? Can you post child component code?
0

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.

4 Comments

Yeh looks like that... Can you recommend another way of triggering the function of the child?
@rmcsharry - Are you sure about your note 2? See this stackblitz.
@ConnorsFan Ok, it seems your stackblitz disproves my point 2 - maybe it is because he is loading the modal using the modalService, and that is causing #traceRef to always be undefined (so not quite the same as your ngIf which delays adding the child to the DOM).
@merstik I added the full code for EventEmitter idea - passing the function directly as part of the even payload. It should work, but you will need to figure out the best way to call the actual function in the handle method (instead of console.log of course).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.