Angular 6 nested ViewChild inside ng-template is null

12,290

Solution 1

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

Solution 2

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

Solution 3

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;
    }
}
Share:
12,290

Related videos on Youtube

Erwol
Author by

Erwol

React/Angular@TS &amp; Django SSE.

Updated on June 04, 2022

Comments

  • Erwol
    Erwol almost 2 years

    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

  • Amit Chigadani
    Amit Chigadani over 5 years
    This could be a problem when there are multiple app-audio tags in a template. That is the reason we have template reference. And coming to your solution, how does that differ from using template reference? Aren't they both same?
  • rrd
    rrd over 5 years
    Can you use ViewChildren() in that case then @AmitChigadani ?
  • Erwol
    Erwol over 5 years
    Hello Rahul, and thanks for your idea. Unfortunately it's still not working. I think the problem comes because the modal content is not rendered till the modal itself is opened. And as ViewChild properties are set before ngAfterViewInit, and our modal is opened manually by the user, for some reason change detector doesn't get the changes here and this.audio is still null.
  • Admin
    Admin over 5 years
    @AmitChigadani @ViewChild('audio', { observe: AudioComponent }) and no, they aren't the same, view children inspect the HTML tag by default.
  • Rahul
    Rahul over 5 years
    @AmitChigadani template reference will not be available when the page loads - since it has been inside the <ng-template> so when you read the component directly that might work - seems to be an idea not sure about it - it worked for me in one scenario
  • Amit Chigadani
    Amit Chigadani over 5 years
    @trichetriche He is still using @ViewChild with component name and not ViewChildrenwhich is same as using @ViewChild with template reference
  • Admin
    Admin over 5 years
    @AmitChigadani @ViewChild('audio', { observe: AudioComponent }) uses the name of the template variable, so no, you don't use the component name anymore. You use the class to explicitly state what to observe.
  • Amit Chigadani
    Amit Chigadani over 5 years
    Agreed, but in the answer above, template reference is not being used. That is what I am pointing out here.
  • Admin
    Admin over 5 years
    Well that's beside the point since the OP doesn't have several instances, and I have been answering you on how to be able to use the variable name anyway.
  • Erwol
    Erwol over 5 years
    Hi @RahulSwamynathan, I updated my question with a code example, in case you want to check it out. Thanks.
  • Rahul
    Rahul over 5 years
    @Erwol I have updated the ans with some changes - Please have a look
  • Erwol
    Erwol over 5 years
    Thanks, this is what helped me to figure out the final solution. I created a method for the modal component called setAudioComponent, that is called by the user by interacting with the modal. This method receives the #audio component and sets the private property.
  • Geo242
    Geo242 about 2 years
    In certain situations I think this is a perfectly reasonable approach. Especially when the content within the template is not rendered when the host component is initially instantiated. This is a more reactive approach.