Skip to content

What is the correct way to call a method of the host or parent component from a dynamically added component?

I want to know the correct way to call the greet() method declared in the host component from a dynamically added component

src/app/app.component.ts

import {
  Component,
  ViewChild,
  ComponentFactoryResolver,
  ViewContainerRef,
} from '@angular/core';

import { OneComponent } from './application/one/one.component';
import { TwoComponent } from './application/two/two.component';
import { ThreeComponent } from './application/three/three.component';
import { AdHostDirective } from './ad-host.directive';

enum Target {
  ONE = 'one',
  TWO = 'two',
  THREE = 'three',
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  @ViewChild(AdHostDirective, { static: true }) adHost: AdHostDirective;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  toggle(target: string): void {
    let componentFactory: any;

    switch (target) {
      case Target.ONE:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          OneComponent
        );
        break;
      case Target.TWO:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          TwoComponent
        );
        break;
      case Target.THREE:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          ThreeComponent
        );
        break;
      default:
        break;
    }

    const viewContainerRef = this.adHost.viewContainerRef;

    viewContainerRef.clear();
    viewContainerRef.createComponent(componentFactory);
  }

  greet(): void {
    alert('Hi');
  }
}

src/app/ad-host.directive.ts

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appAddHost]',
})
export class AdHostDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

src/app/app.component.html

<button (click)="toggle('one')">One</button>
<button (click)="toggle('two')">Two</button>
<button (click)="toggle('three')">Three</button>

<ng-template appAddHost></ng-template>

In my case there are three components that are dynamically added in all of them I need to call a method in the host component, for example in component One

src/app/application/one/one.component.ts

import { Component, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-one',
  templateUrl: './one.component.html',
  styleUrls: ['./one.component.css'],
})
export class OneComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}

  onClick(): void {
    // how to call host component greet method?
  }
}

src/app/application/one/one.component.html

<p>one works!</p>

<button (click)="onClick()">On click</button>

Update 1

I share the repository of the listed examples to facilitate your collaboration

https://github.com/ilmoralito/add-components-dynamically-demo-app

Thanks for your comments

Answer

You can add an output() in OneComponent then you subscribe in your AppComponent like below:

import { Component, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-one',
  templateUrl: './one.component.html',
  styleUrls: ['./one.component.css'],
})
export class OneComponent implements OnInit {
  
  @Output()
  greetEvent: EventEmitter<void> = new EventEmitter<void>();

  constructor() {}

  ngOnInit(): void {}

  onClick(): void {
    // how to call host component greet method?
    this.greetEvent.emit();
  }
}

src/app/app.component.ts

import {
  Component,
  ViewChild,
  ComponentFactoryResolver,
  ViewContainerRef,
} from '@angular/core';

import { OneComponent } from './application/one/one.component';
import { TwoComponent } from './application/two/two.component';
import { ThreeComponent } from './application/three/three.component';
import { AdHostDirective } from './ad-host.directive';

enum Target {
  ONE = 'one',
  TWO = 'two',
  THREE = 'three',
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  @ViewChild(AdHostDirective, { static: true }) adHost: AdHostDirective;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  toggle(target: string): void {
    let componentFactory: any;

    switch (target) {
      case Target.ONE:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          OneComponent
        );
        break;
      case Target.TWO:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          TwoComponent
        );
        break;
      case Target.THREE:
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(
          ThreeComponent
        );
        break;
      default:
        break;
    }

    const viewContainerRef = this.adHost.viewContainerRef;

    viewContainerRef.clear();
    const componentRef: ComponentRef<any> = viewContainerRef.createComponent(componentFactory);
    componentRef.instance.greetEvent.subscribe(() => this.greet());
  }

  greet(): void {
    alert('Hi');
  }
}