derstauner
derstauner

Reputation: 1806

inject component in template dynamically from directive

I have this simplified html structure in a template:

<div>
  <grid
    class="enhanced"
    enhancedGrid
    [infoTooltipContainer]="infoTooltipViewRef"
  >
  </grid>
</div>
<div #infoTooltip></div>

In the component file I read the ViewContainerRef of the infoTooltip div the usual way:

@ViewChild('infoTooltip', { static: true, read: ViewContainerRef }) infoTooltipViewRef!: ViewContainerRef;

As you can see, I assign this variable to an input property, which the directive uses later.

On the grid I have in some cells simple icons. Hovering this icons a tooltip appears. The content of the tooltip can be simple string or an other component. This other component is defined the standard way (html, css, ts, module) in the file structure of the project. And there can be multiple such components. This components can be then assigned to a tooltip object. In order to assign this components to the tooltips, we have to inject this in the ts file of the grid component, f. e.:

customTooltipComp: InfoTooltipComponent = inject(InfoTooltipComponent);

And use it at declaring the tooltips, f. e.:

{
      name: 'feb - 8',
      columnField: 'feb',
      rowField: 'id',
      rowValue: 8,
      icon: '<span class="material-symbols-outlined">info</span>',
      content: this.customTooltipComp,
    }

Now, in the directive, hovering on an icon, if the type of the content variable isn't a string, then I want to create the provided custom component like this:

config.infoTooltipContainer.createComponent(tooltip.content);

infoTooltipContainer is a ViewContainerRef and has it's value from the input property of the directive (I checked it via simple console.log).

But upon creating the custom component, I get this error in the above line:

componentFactory.create is not a function

Which I don't understand, because the infoTooltipContainer is initialized properly (at least I think so and consoling it has also it's right value).

So, how to solve it?

Upvotes: 0

Views: 41

Answers (2)

derstauner
derstauner

Reputation: 1806

Finally I found the right way to do this.

I don't need any component injection via DI like

customTooltipComp: InfoTooltipComponent = inject(InfoTooltipComponent);

Instead I only have to pass the InfoTooltipComponent like this:

{
      name: 'feb - 8',
      columnField: 'feb',
      rowField: 'id',
      rowValue: 8,
      icon: '<span class="material-symbols-outlined">info</span>',
      content: InfoTooltipComponent,
    }

Now, it works as expected.

Upvotes: 0

Chintan
Chintan

Reputation: 146

  • Use ComponentFactoryResolver to create component factory
  • Clear container before creating new component to prevent multiple instances
import { 
  Directive, 
  Input, 
  ViewContainerRef, 
  ComponentFactoryResolver, 
  Type 
} from '@angular/core';

@Directive({
  selector: '[tooltipInjector]'
})
export class TooltipInjectorDirective {
  @Input() infoTooltipContainer!: ViewContainerRef;

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver
  ) {}

  createDynamicTooltip(componentType: Type<any>) {
    // Clear any existing components
    this.infoTooltipContainer.clear();

    // Create component factory
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);

    // Create component and inject into container
    this.infoTooltipContainer.createComponent(componentFactory);
  }

  // If using standalone components in Angular 14+
  createDynamicTooltipStandalone(componentType: Type<any>) {
    this.infoTooltipContainer.clear();
    this.infoTooltipContainer.createComponent(componentType);
  }
}

Upvotes: 0

Related Questions