Search code examples
angulartypescriptng-component-outlet

How do I access the component instance from code?


I am using an ngComponentOutlet to dynamically display a caller-supplied component. My code knows absolutely nothing about the supplied component type. The template boils down to this:

<div [innerText]="options.title"></div>
<ng-container *ngComponentOutlet="options.componentType"></ng-container>
<button (click)="showInfo()">Info</button>

Speaking in a simplified manner, the caller for my UI passes a config object similar to the following:

export interface DisplayOptions<TComponent> {
  readonly componentType: Type<TComponent>;
  
  readonly title: string;
  
  readonly onClickInfo: (cmp: TComponent) => void;
}

The UI surrounding the component outlet contains a couple of buttons, such as an Info button shown above. When that Info button is clicked, the onClickInfo function should be invoked, which needs to receive a reference to the component.

Inside that showInfo() function, how do I get my hands on the reference to the component instance in the component outlet?


Solution

  • You should be able to get a reference to this component using a @ViewChild.

    1. Make sure you have imported NgComponentOutlet.
    2. Import ViewChild
    3. Create a @ViewChild to get a reference to the component directive:
    • @ViewChild(NgComponentOutlet, { static: false }) ngComponentOutlet: NgComponentOutlet;
    1. Modify your show info:
    • showInfo() { if (this.ngComponentOutlet) { const componentInstance = this.ngComponentOutlet['_componentRef'].instance; this.options.onClickInfo(componentInstance); } }

    A full example would look something like this:

    import { Component, Type, ViewChild, NgComponentOutlet } from '@angular/core';
    
    export interface DisplayOptions<TComponent> {
      readonly componentType: Type<TComponent>;
      readonly title: string;
      readonly onClickInfo: (cmp: TComponent) => void;
    }
    
    @Component({
      selector: 'app-dynamic',
      template: `
        <div [innerText]="options.title"></div>
        <ng-container *ngComponentOutlet="options.componentType"></ng-componentOutlet>
        <button (click)="showInfo()">Info</button>
      `
    })
    export class DynamicComponent<TComponent> {
      @ViewChild(NgComponentOutlet, { static: false }) ngComponentOutlet: NgComponentOutlet;
      options: DisplayOptions<TComponent>;
    
      showInfo() {
        if (this.ngComponentOutlet) {
          const componentInstance = this.ngComponentOutlet['_componentRef'].instance;
          this.options.onClickInfo(componentInstance);
        }
      }
    }
    

    You query the ComponentOutlet using the @ViewChild and store it in the ngComponentOutlet prop. showInfo() can then access the ['_componentRef'].instance prop of ngComponentOutlet.

    Another way to do this would be to go with an @Input which is similar, but you'd have to update the HTML and pass in a primary key that you can use like an Id.