14

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

Erwol
  • 1,911
  • 2
  • 23
  • 28
  • Please provide a [mcve] reproducing your issue. –  Nov 08 '18 at 12:13
  • Hi @trichetriche, it's as simple as this. I cannot reference the audio component even after the modal has been opened and it's content rendered. When leaving app-audio outside the modal everything works fine. – Erwol Nov 08 '18 at 12:22
  • I understood your issue. I'm asking you to provide an example of it in a sandbox. –  Nov 08 '18 at 12:29
  • Not sure whether this is any helpful or not. But, thought to share with you anyway. https://stackoverflow.com/questions/41669787/how-to-change-parent-variable-from-children-component/51384602#51384602 – Anjana Silva Nov 08 '18 at 12:38
  • Sorry @trichetriche, it's a busy day. I reproduced the issue here: https://stackblitz.com/edit/angular-ofmpju – Erwol Nov 08 '18 at 12:56

3 Answers3

6

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

Amit Chigadani
  • 28,482
  • 13
  • 80
  • 98
  • 1
    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. – Erwol Nov 08 '18 at 19:38
5

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;
    }
}
  • 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. – Geo242 Feb 24 '22 at 19:13
4

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

Rahul
  • 2,040
  • 2
  • 11
  • 29
  • 1
    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? – Amit Chigadani Nov 08 '18 at 12:04
  • Can you use ViewChildren() in that case then @AmitChigadani ? – rrd Nov 08 '18 at 12:09
  • 1
    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. – Erwol Nov 08 '18 at 12:09
  • @AmitChigadani `@ViewChild('audio', { observe: AudioComponent })` and no, they aren't the same, view children inspect the HTML tag by default. –  Nov 08 '18 at 12:13
  • @AmitChigadani template reference will not be available when the page loads - since it has been inside the `` 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 – Rahul Nov 08 '18 at 12:15
  • @trichetriche He is still using `@ViewChild` with component name and not `ViewChildren`which is same as using `@ViewChild` with template reference – Amit Chigadani Nov 08 '18 at 12:18
  • @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. –  Nov 08 '18 at 12:20
  • Agreed, but in the answer above, template reference is not being used. That is what I am pointing out here. – Amit Chigadani Nov 08 '18 at 12:22
  • 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. –  Nov 08 '18 at 12:30
  • Hi @RahulSwamynathan, I updated my question with a code example, in case you want to check it out. Thanks. – Erwol Nov 08 '18 at 13:19
  • @Erwol I have updated the ans with some changes - Please have a look – Rahul Nov 08 '18 at 14:12