Create Tooltip with createComponent

2 분 소요

Let’s build a dynamic tooltip that appears on hover, leveraging Angular’s powerful createComponent API for dynamic component rendering.

Basic Operation

  • Create two tooltip components (ATooltipComponent, BTooltipComponent) as standalone.
  • Create a container component to host the elements that will trigger the tooltips, also as standalone.
  • Create a standalone directive that listens for mouseenter and mouseleave events, then dynamically creates and destroys the appropriate tooltip component using createComponent.

Creating the Components

The Container Component

We’ll create a host for our tooltips and apply our custom tooltip directive to trigger them.

// src/app/container.component.ts
import { Component } from '@angular/core';
import { TooltipDirective } from './tooltip.directive';

@Component({
  selector: 'app-container',
  standalone: true,
  imports: [TooltipDirective], // Import the directive to use it in the template
  template: `
    <h2>Hover over these elements</h2>
    <div class="box" [tooltip]="'A'">Show Tooltip A</div>
    <div class="box" [tooltip]="'B'">Show Tooltip B</div>
  `,
  styles: `
    .box {
      border: 1px solid #ccc;
      padding: 20px;
      margin: 20px;
      cursor: pointer;
      width: 200px;
      text-align: center;
    }
  `
})
export class ContainerComponent {}

The Dynamic Tooltip Components

These are the simple components that will be dynamically rendered as tooltips.

// src/app/a-tooltip.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'a-tooltip',
  standalone: true,
  template: `<div class="tooltip">Tooltip A: Hello World</div>`,
  styles: `.tooltip { background-color: lightblue; padding: 10px; border-radius: 5px; }`
})
export class ATooltipComponent {}
// src/app/b-tooltip.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'b-tooltip',
  standalone: true,
  template: `<div class="tooltip">Tooltip B: Goodbye</div>`,
  styles: `.tooltip { background-color: lightcoral; color: white; padding: 10px; border-radius: 5px; }`
})
export class BTooltipComponent {}

Creating the Directive

This is the core logic of our example. The directive detects events and, based on its input (‘A’ or ‘B’), dynamically creates and destroys the correct tooltip component.

// src/app/tooltip.directive.ts
import {
  Directive,
  Input,
  HostListener,
  ComponentRef,
  ViewContainerRef,
  inject,
  Type
} from '@angular/core';
import { ATooltipComponent } from './a-tooltip.component';
import { BTooltipComponent } from './b-tooltip.component';

@Directive({
  selector: '[tooltip]',
  standalone: true,
})
export class TooltipDirective {
  @Input('tooltip') type: 'A' | 'B' | string = '';

  private componentRef?: ComponentRef<any>;
  // Inject ViewContainerRef using the inject function
  private viewContainerRef = inject(ViewContainerRef);

  @HostListener('mouseenter')
  onMouseEnter(): void {
    if (this.componentRef) return; // Don't create if a tooltip already exists

    const componentToCreate = this.getComponentType(this.type);
    if (!componentToCreate) return;

    // Pass the component class directly, no ComponentFactoryResolver needed
    this.componentRef = this.viewContainerRef.createComponent(componentToCreate);
  }

  @HostListener('mouseleave')
  onMouseLeave(): void {
    if (this.componentRef) {
      this.componentRef.destroy();
      this.componentRef = undefined;
    }
  }

  private getComponentType(type: string): Type<any> | null {
    switch (type) {
      case 'A':
        return ATooltipComponent;
      case 'B':
        return BTooltipComponent;
      default:
        return null;
    }
  }
}
  • inject(ViewContainerRef): This is the modern, constructor-less way to handle dependency injection.
  • createComponent(componentToCreate): Since Angular v13, you can pass the component class directly to create an instance, which makes the code much cleaner and removes the need for ComponentFactoryResolver.
  • componentRef.destroy(): This safely removes the created component instance from the DOM and helps prevent memory leaks.

Note: Configuration in a Standalone Environment

Unlike the old NgModule approach, in a standalone world, each component and directive must explicitly declare its dependencies in its imports array.

  • The ContainerComponent uses the TooltipDirective in its template, so it must add TooltipDirective to its imports array.
  • The TooltipDirective programmatically creates ATooltipComponent and BTooltipComponent. Since it doesn’t use them in a template, it doesn’t need to add them to an imports array. The standard TypeScript import statement is sufficient to get their class types.

Now, when you run this example and hover over each div, you’ll see the corresponding tooltip dynamically created and destroyed.

댓글남기기