🐟

[Angular] カスタムパイプで生成したinnerHTMLのbutton要素にクリックイベントを付与する

2023/04/27に公開

はじめに

カスタムパイプで動的に生成したHTMLのbutton要素にクリックイベントを付与するのに、結構詰まったので備忘録として残しておきます。

HTMLタグを動的にコンポーネントに埋め込む

カスタムパイプを用いてHTML(文字列)をinnerHTML属性にバインドすることでHTMLを動的に生成することができます。

src\app\shared\pipe\buttonHtml.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'buttonHtml'
})
export class ButtonHtmlPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(value: string): any {
    const html = `<button>${value}</button>`;
    return this.sanitizer.bypassSecurityTrustHtml(html);
  }
}
src\app\inner-htmlcustom-pipe\inner-htmlcustom-pipe.component.ts
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-inner-htmlcustom-pipe',
  templateUrl: './inner-htmlcustom-pipe.component.html',
  styleUrls: ['./inner-htmlcustom-pipe.component.scss']
})
export class InnerHTMLCustomPipeComponent implements OnInit, AfterViewInit {
  @ViewChild('customButton') customButton?: ElementRef;

  customButtonElement: string = 'Click me!';

  constructor() { }

  ngOnInit(): void {
  }
}
src\app\inner-htmlcustom-pipe\inner-htmlcustom-pipe.component.html
<div [innerHTML]="customButtonElement | buttonHtml"></div>

動的に生成したbuttonタグにイベントを付与する

次は、カスタムパイプとinnerHTML属性で動的に生成したbutton属性にイベントを付与させます。
ただ、カスタムパイプ内で(click)イベントをバインドすることができません。

// 使えない
const html = `<button (click)="onClick()">${value}</button>`;

となると、@viewChild()で直接カスタムパイプのinnerHTMLを参照して、clickイベントを登録したいところですが、@viewChild()で直接innerHTMLの要素を参照することはできません。

src\app\shared\pipe\buttonHtml.pipe.ts
// テンプレート変数もしくはidを設定してみる
const html = `<button #customButton>${value}</button>`;
src\app\inner-htmlcustom-pipe\inner-htmlcustom-pipe.component.ts
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-inner-htmlcustom-pipe',
  templateUrl: './inner-htmlcustom-pipe.component.html',
  styleUrls: ['./inner-htmlcustom-pipe.component.scss']
})
export class InnerHTMLCustomPipeComponent implements OnInit, AfterViewInit {
  @ViewChild('customButton') customButton?: ElementRef;

  customButtonElement: string = 'Click me!';

  constructor() { }

  ngOnInit(): void {
  }

  ngAfterViewInit() {
  console.log(this.customButton)
    // undefined
  }
}
  • 参照できずundefinedになる

[解決法] innerHTMLを展開する要素から参照する

解決法としては、innerHTMLを展開する要素から参照します。
カスタムパイプ内では、どのbutton要素か特定するためにidを付与しておきます。

src\app\shared\pipe\buttonHtml.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'buttonHtml'
})
export class ButtonHtmlPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(value: string): any {
    const html = `<button id="customButton">${value}</button>`;
    return this.sanitizer.bypassSecurityTrustHtml(html);
  }
}
src\app\inner-htmlcustom-pipe\inner-htmlcustom-pipe.component.html
<div #customButton [innerHTML]="customButtonElement | buttonHtml"></div>
src\app\inner-htmlcustom-pipe\inner-htmlcustom-pipe.component.ts
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-inner-htmlcustom-pipe',
  templateUrl: './inner-htmlcustom-pipe.component.html',
  styleUrls: ['./inner-htmlcustom-pipe.component.scss']
})
export class InnerHTMLCustomPipeComponent implements OnInit, AfterViewInit {
  @ViewChild('customButton') customButton?: ElementRef;

  customButtonElement: string = 'Click me!';

  constructor() { }

  ngOnInit(): void {
  }

  ngAfterViewInit() {
    const customButtonElement = this.customButton?.nativeElement.querySelector('#customButton');
    if(customButtonElement) {
      customButtonElement.addEventListener('click', () => {
        console.log('クリックしたよ')
      })
    }
  }
}
  • @viewChild()でテンプレート変数customButtonを取得
  • querySelector()で任意のidの要素を取得
  • 取得したbutton要素にaddEventListenerclickイベントを登録

こんな特殊な書き方をするケースはあるのでしょうか。。他に良い方法があれば随時更新します。

Discussion

ログインするとコメントできます