こんにちは。ゴーリスト開発の飯尾です。
AngularでWebアプリを作ったりしています。
世の賢人によるイルなコンポーネントだと微妙に機能が多すぎたり足りなかったりで
結局自作のワックコンポーネント生産したりしますよね。
作りたいもの
イメージとしてはこんなやつです。
参考にしたもの
基本的にはこれをパクっ参考にしましたが
ここのところがちょっとな〜だったので書き換えました。
@HostListener('click', ['$event']) onClick(event: Event) { if (!this.hideOnClick) { event.stopPropagation(); } }
変えたいポイント
その1
ドロップダウンを開いている時のみクリックイベントを拾いたい(@HostListenerでイベント登録するとリムーブできない)
その2
iOSだとdocumentをルート要素としてクリックイベントを登録しても拾ってくれない
解決法を考える
ドロップダウンを開いている時のみクリックイベントを拾いたい 問題
Observableでクリックイベントを制御する
// ドロップダウン開いてる時 const stream = Observable.fromEvent(document, 'click').subsucribe(() => { console.log('clicked!'); }); // 閉じたら購読をやめる stream.unsubscribe();
iOSだとdocumentをルート要素としてクリックイベントを登録しても拾ってくれない 問題
Observable.fromEvent(document, 'touchend')
ならOK
そうしてできたもの
import { ChangeDetectorRef, Component, ElementRef, HostBinding, Injectable, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { Observable, Subscriber } from 'rxjs/Rx'; @Injectable() export class DropdownRegistry { private dropdownMenuComponents: any[] = []; constructor() { } public add(dropdownMenu: DropdownMenuComponent) { this.dropdownMenuComponents.push(dropdownMenu); } public remove(dropdownMenu: DropdownMenuComponent) { this.dropdownMenuComponents.slice(this.dropdownMenuComponents.indexOf(dropdownMenu), 1); } public hideAllExcept(dropdownMenu: DropdownMenuComponent) { this.dropdownMenuComponents.forEach((component) => { if (component !== dropdownMenu) { component.hide(); } }); } } @Component({ selector: 'app-dropdown-menu', template: `<ng-content></ng-content>`, encapsulation: ViewEncapsulation.None // ここはカプセル化しない }) export class DropdownMenuComponent implements OnInit, OnDestroy { @HostBinding('class.is-visible') isVisible: boolean = false; private clickEventStream: any; // Observable<Event>とかObservable<any>だと警告出る private subscription: Subscriber<any>; constructor( private changeDetectionRef: ChangeDetectorRef, private dropdownRegistry: DropdownRegistry, private elementRef: ElementRef, ) { this.dropdownRegistry.add(this); } ngOnInit() { // iOSだとボタンとかリンクの要素以外はタッチしてもクリックイベントとしてみなされない this.clickEventStream = Observable.merge(Observable.fromEvent(document, 'click'), Observable.fromEvent(document, 'touchend')) .catch(e => Observable.throw(e)); } ngOnDestroy() { this.dropdownRegistry.remove(this); } public toggle(event: Event): void { if (this.isVisible) { this.hide(); } else { this.show(event); } } public hide(): void { this.isVisible = false; if (this.subscription && !this.subscription.closed) { // stream流れてるなら止める this.subscription.unsubscribe(); } this.changeDetectionRef.markForCheck(); } public show(event: Event): void { event.stopPropagation(); this.hideAll(); this.isVisible = true; this.subscription = this.clickEventStream.subscribe((clickEvent: Event) => { // 外側クリック時に閉じる clickEvent.stopPropagation(); if (this.isVisible && !this.elementRef.nativeElement.contains(clickEvent.target)) { this.hide(); } }); } private hideAll(): void { this.dropdownRegistry.hideAllExcept(this); } }
あとはドロップダウン表示用のトグルボタンとCSSをいいかんじにする
いいかんじにする
こんな感じで動いているよ
おわりに
もっとドープな書き方を教えてくれるマイメンを募集しています!!