Angularで範囲外をクリックしたイベントを取得する


やりたいこと

「開いているウィンドウの範囲外をクリックしたら、ウィンドウを閉じる」といった動きを実現する際に、範囲外をクリックしたイベントを取得したい。

jQueryで言うところの、
範囲外クリックでポップアップを閉じる正しい JQuery

自分で実装

Output、EventEmitterの仕組みを使えば、簡単に実装できました。
イメージ的には、「子コンポーネントでグローバルなクリックイベントを拾って、そのイベントが自分に対するものではなかったら、親コンポーネントにイベントを投げる」と言った感じです。

子コンポーネント

Output, EventEmitter, HostListener, ElementRefをインポートし、ElementRefをconstructorでDIします。

child.component.ts
import { Output, EventEmitter, HostListener, ElementRef } from '@angular/core';

constructor(
    private elementRef: ElementRef
) {}

コンポーネントの範囲外がクリックされたことを親コンポーネントに通知するためのイベントのバインディングを定義します。

child.component.ts
@Output() clickOutside = new EventEmitter<MouseEvent>();

HostListenerデコレータを宣言した後に、範囲外がクリックされたことを検知するメソッドをバインドします。

child.component.ts
  onClickOutsidefDetailSearch(event: MouseEvent, targetElement: HTMLElement) {
    const clickedInside = this.elementRef.nativeElement.contains(targetElement);
    if (!clickedInside) {
      this.clickOutside.emit(event);
    }
  }

親コンポーネント

clickOutsideイベントを受けるメソッドを定義します。

parent.component.ts
  onClickOutside(event: MouseEvent) {
        console.log(event);
        // 何かする
  }
parent.component.html
<app-child (clickOutside)="onClickOutside($event)"> My element </app-child>

ライブラリを使う

ng-click-outside というライブラリがあるようです。

先に紹介した自分で実装する方法では、個々のコンポーネントに一連の実装を行う必要がありましたが、こちらのライブラリでは、ディレクティブとして機能が使えるため、めちゃ楽です。

使ってみる

インストール

$ npm install --save ng-click-outside

インポート

app.module.ts
import { ClickOutsideModule } from 'ng-click-outside';

@NgModule({
  ...
  imports: [
    ...
    ClickOutsideModule
    ],
    ...
})
class AppModule {}

clickOutsideイベントを受けるメソッドを定義します。

parent.component.ts
  onClickOutside(event: MouseEvent) {
        console.log(event);
        // 何かする
  }

好きなエレメント、コンポーネントにディレクティブを追加します。

parent.component.html
<div (clickOutside)="onClickOutside($event)">My element</div>

参考

https://qiita.com/mabots/items/74c21ebcedf0004f7fb5
https://github.com/valor-software/ngx-bootstrap/issues/1823

終わりに

今回紹介した内容を実装するにあたって、参考に記載したgithubのissueを参考に自分で実装した後に、便利なライブラリがあることに気づきました...
HostListenerをの使い方を再確認するいい機会になったかと思います。
とりあえずng-click-outsideを使いましょう。