こんにちは。ゴーリスト開発の飯尾です。
AngularでWebアプリを作ったりしています。
世の賢人によるイルなコンポーネントだと微妙に機能が多すぎたり足りなかったりで
結局自作のワックコンポーネント生産したりしますよね。
作りたいもの
- Airbnbや一休の検索条件指定部分みたいなドロップダウンメニュー
- 内側クリックで閉じず、外側クリックで閉じる
- 一画面でひとつだけ開く
イメージとしてはこんなやつです。
参考にしたもの
基本的にはこれをパクっ参考にしましたが
ここのところがちょっとな〜だったので書き換えました。
@HostListener('click', ['$event']) onClick(event: Event) {
if (!this.hideOnClick) {
event.stopPropagation();
}
}
変えたいポイント
その1
ドロップダウンを開いている時のみクリックイベントを拾いたい(@HostListenerでイベント登録するとリムーブできない)
github.com
その2
iOSだとdocumentをルート要素としてクリックイベントを登録しても拾ってくれない
qiita.com
解決法を考える
ドロップダウンを開いている時のみクリックイベントを拾いたい 問題
Observableでクリックイベントを制御する
qiita.com
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;
private subscription: Subscriber<any>;
constructor(
private changeDetectionRef: ChangeDetectorRef,
private dropdownRegistry: DropdownRegistry,
private elementRef: ElementRef,
) {
this.dropdownRegistry.add(this);
}
ngOnInit() {
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) {
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をいいかんじにする
いいかんじにする
こんな感じで動いているよ
おわりに
もっとドープな書き方を教えてくれるマイメンを募集しています!!