Angular 6 nested ViewChild inside ng-template is null

Viewed 13635

We are using a modal (ng-bootstrap's one) in our application. That modal looks like:

<ng-template #modal let-modal>
    <app-audio #audio></app-audio>
</ng-template>

And it's logic:

@ViewChild('modal')
modal: ElementRef;

@ViewChild('audio')
audio: AudioComponent;

The modal is opened with:

this.modalService.open(this.modal, { size: 'lg' });

Everything fine till here. The modal opens and the audio component is shown. But now, we want to access the logic that is inside the component, and when doing something like:

this.audio.somePublicComponentFunction()

It happens that this.audio is null. I have already tried to get the child with angular's change detector, but cannot find a way to properly link this.audio with the actual component. Any ideas? Thanks a lot.

You can see the issue here: stackblitz.com/edit/angular-ofmpju

3 Answers

You can call the method audio.someFunction() from the template itself.

<ng-template #modal let-modal>
  <div style="background-color: red;"> 
  <h1>Modal header</h1>
  <app-audio #audio></app-audio>
  <!-- on click, call audio comp method someFunction() using its reference --> 
  <button (click)="audio.someFunction()">Operate with audio from inside modal</button>
  </div>
</ng-template>

No need of @ViewChild property here. This should do the trick for you.

Forked demo

You can read the child component without the refrence variable like this

@ViewChild(AudioComponent)
audio: AudioComponent;

This will give you the instance of the child component - where you can access the method

this.audio.someComponentFunction()

Your html

<ng-template #modal let-modal>
    <app-audio></app-audio>
</ng-template>

This will solve your issue i think - Happy coding

Update:

Hope i found a workaround for this issue - if in case you want to trigger only one function you can use this method

I have just added a property with getter and setter and triggered the function when we set the value

@Input()
  get triggerFunction(): boolean {
    return this.runFuntion;
  }

  set triggerFunction(value: boolean) {
    this.runFuntion = value;
    this.someFunction();
  }  

So this causes to trigger the function every time when the model show up - property mentioned above belongs to the child component which is nested inside the <ng-template> so finally the model template will read as mentioned below:

<ng-template #modal let-modal>
  <app-audio [triggerFunction]="true"></app-audio>
</ng-template>

Hope this will act a workaround for now - Thanks

For me all this solutions did not work and I still wanted to access my own component inside a third party ng-template. Here is my 'solution'. I don't think this is best practice but a desperate solution to get what I want ;-) It only works for your own components of course.

// mycomponent.ts => component that needs to be accessed 
import { Component, Output, EventEmitter, AfterViewInit } from '@angular/core';

@Component({
    selector: 'my-component',
    templateUrl: './mycomponent.html'
})
export class MyComponent implements AfterViewInit {
   @Output() initialized: EventEmitter<MyComponent> = new EventEmitter<MyComponent>();

   ngAfterViewInit(): void {
        this.initialized.emit(this);
   }

   reload(): void {
        // Do something
   }
}


// somecomponent.html => component with <ng-template> holding MyComponent

<ng-template>
    <div class="btn-group ml-2">
      <my-component (initialized)="onMyComponentInitialized($event)"></my-component>
    </div>
</ng-template>


// somecomponent.ts => component with <ng-template> holding MyComponent
import { Component, OnDestroy } from '@angular/core';
import { MyComponent } from '../../my-component';

@Component({
    selector: 'some-component',
    templateUrl: './some-component.html'
})
export class SomeComponent implements OnDestroy {
    private _myComponent: MyComponent = null;

    onMyComponentInitialized(component: MyComponent) {
        this._myComponent = component;
    }

    someOtherMethod() {
        if (this._myComponent) {
            // Call some method on the component
            this._myComponent.reload();
        }
    }

    ngOnDestroy() {
        this._myComponent = null;
    }
}
Related