外側のクリックでドロップダウンを閉じるにはどうすればよいですか?


144

ユーザーがドロップダウン以外の場所をクリックしたときにログインメニューのドロップダウンを閉じたいのですが、Angular2とAngular2の "アプローチ"でそれを実行したいのですが...

ソリューションを実装しましたが、自信がありません。同じ結果を達成する最も簡単な方法がなければならないと私は思います、あなたが何かアイデアがあれば...議論しましょう:)!

これが私の実装です:

ドロップダウンコンポーネント:

これは私のドロップダウンのコンポーネントです:

  • このコンポーネントが可視に設定されるたびに(たとえば、ユーザーがボタンをクリックしてそれを表示すると)、SubjectsService内に格納された「グローバル」なrxjsサブジェクトuserMenuにサブスクライブします。
  • そして、それが非表示になるたびに、この件名の購読を解除します。
  • このコンポーネントのテンプレートの任意の場所をクリックするたびに、onClick()メソッドがトリガーされます。これにより、上部(およびアプリケーションコンポーネント)へのイベントバブリングが停止します。

これがコードです

export class UserMenuComponent {

    _isVisible: boolean = false;
    _subscriptions: Subscription<any> = null;

    constructor(public subjects: SubjectsService) {
    }

    onClick(event) {
        event.stopPropagation();
    }

    set isVisible(v) {
        if( v ){
            setTimeout( () => {
this._subscriptions =  this.subjects.userMenu.subscribe((e) => {
                       this.isVisible = false;
                       })
            }, 0);
        } else {
            this._subscriptions.unsubscribe();
        }
        this._isVisible = v;
    }

    get isVisible() {
        return this._isVisible;
    }
}

アプリケーションコンポーネント:

一方、アプリケーションコンポーネント(ドロップダウンコンポーネントの親)があります。

  • このコンポーネントは、すべてのクリックイベントをキャッチし、同じrxjsサブジェクト(userMenu)で発行します

これがコードです:

export class AppComponent {

    constructor( public subjects: SubjectsService) {
        document.addEventListener('click', () => this.onClick());
    }
    onClick( ) {
        this.subjects.userMenu.next({});
    }
}

何が私を困らせましたか:

  1. これらのコンポーネント間のコネクタとして機能するグローバルなサブジェクトを使用するという考えには、あまり慣れていません。
  2. setTimeout:ここでは、ユーザーがドロップダウンを表示するボタンをクリックした場合そう起こるものですので、これが必要とされています。
    • ユーザーがボタン(ドロップダウンコンポーネントの一部ではない)をクリックして、ドロップダウンを表示します。
    • ドロップダウンが表示され、すぐにuserMenuサブジェクトにサブスクライブします。
    • クリックイベントがアプリコンポーネントまでバブルアップしてキャッチされる
    • アプリケーションコンポーネントは、userMenuサブジェクトでイベントを発行します
    • ドロップダウンコンポーネントは、userMenuでこのアクションをキャッチし、ドロップダウンを非表示にします。
    • 最後に、ドロップダウンは表示されません。

この設定されたタイムアウトにより、現在のJavaScriptコードターンの最後までサブスクリプションが遅延し、問題が解決しますが、私の意見では非常にエレガントな方法です。

よりクリーンで、より良い、よりスマート、より高速、またはより強力なソリューションをご存知の場合は、私にお知らせください:)!


これらの答えはあなたにいくつかのアイデアを与えることがあります。stackoverflow.com/a/35028820/215945stackoverflow.com/questions/35024495#35024651
マーク・Rajcok

回答:


245

あなたは(document:click)イベントを使うことができます:

@Component({
  host: {
    '(document:click)': 'onClick($event)',
  },
})
class SomeComponent() {
  constructor(private _eref: ElementRef) { }

  onClick(event) {
   if (!this._eref.nativeElement.contains(event.target)) // or some similar check
     doSomething();
  }
}

別のアプローチは、カスタムイベントをディレクティブとして作成することです。ベンナデルの投稿をご覧ください。


1
@Sasxaありがとう、同意しました。廃止されていないAPIドキュメントがある場合は、ここに誘導された検索に表示されていたはずです。
danludwig 2016年

4
event.targetが[innerHTML]バインディングなどによって動的に追加された要素である場合、elementRefのnativeElementにはそれが含まれません。
Patrick Graham

8
この手法の唯一の欠点は、アプリケーションにクリックするたびに発生するクリックイベントリスナーがあることです。
codeepic、2017

37
公式のAngular 2スタイルガイドによると、デコレータ@HostListener('document:click', ['$event'])hostプロパティの代わりに使用する必要がありますComponent
のMichałMiszczyszyn

15
または、rxjsを使用することもできます。Observable.fromEvent(document, 'click').subscribe(event => {your code here})たとえば、ドロップダウンを開いたときなど、聞く必要があるときだけサブスクライブし、閉じるときはサブスクライブを解除できます
Blind Despair

42

エレガントな方法

次のclickOutディレクティブを見つけました:https : //github.com/chliebel/angular2-click-outside。私はそれをチェックし、それはうまくいきます(私はclickOutside.directive.ts自分のプロジェクトにのみコピーします)。Uは次のように使用できます。

<div (clickOutside)="close($event)"></div>

closeユーザーがdivの外側をクリックしたときに呼び出される関数はどこにありますか。問題となっている問題を処理するための非常にエレガントな方法です。

上記のディレクティブを使用してpopUpウィンドウを閉じる場合は、まずevent.stopPropagation()popUpを開くボタンクリックイベントハンドラーを追加してください。

ボーナス:

以下で、ファイルから元のディレクティブコードをコピーしますclickOutside.directive.ts(リンクが将来機能しなくなる場合)-著者はChristian Liebelです。


2
@Vega私の推奨は、* ngIfを含む要素でディレクティブを使用することです。ドロップダウンの場合、これは次のようなものになる可能性があります<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Gabriel BalsaCantú18年

19

私はそれをこのようにしました。

ドキュメントにイベントリスナーを追加し、clickそのハンドラーにmy containerが含まれているかどうかをチェックし、含まれevent.targetていない場合はドロップダウンを非表示にします。

このようになります。

@Component({})
class SomeComponent {
    @ViewChild('container') container;
    @ViewChild('dropdown') dropdown;

    constructor() {
        document.addEventListener('click', this.offClickHandler.bind(this)); // bind on doc
    }

    offClickHandler(event:any) {
        if (!this.container.nativeElement.contains(event.target)) { // check click origin
            this.dropdown.nativeElement.style.display = "none";
        }
    }
}

こんにちは。.bind(this)は必要ですか?
Drenai

1
@ブライアンそれは必要かもしれないし必要ないかもしれませんが、彼がthis.offClickHandlerアロー関数でラップした場合は間違いなくそうではありません。
Lansana Camara

17

Sasxaが承認した回答はほとんどの人に有効だと思います。しかし、オフクリックイベントをリッスンする必要がある要素のコンテンツが動的に変化する状況がありました。したがって、動的に作成されたとき、Elements nativeElementにはevent.targetが含まれていませんでした。これを次のディレクティブで解決できます

@Directive({
  selector: '[myOffClick]'
})
export class MyOffClickDirective {

  @Output() offClick = new EventEmitter();

  constructor(private _elementRef: ElementRef) {
  }

  @HostListener('document:click', ['$event.path'])
  public onGlobalClick(targetElementPath: Array<any>) {
    let elementRefInPath = targetElementPath.find(e => e === this._elementRef.nativeElement);
    if (!elementRefInPath) {
      this.offClick.emit(null);
    }
  }
}

elementRefにevent.targetが含まれているかどうかをチェックする代わりに、elementRefがイベントのパス(ターゲットへのDOMパス)にあるかどうかをチェックします。これにより、動的に作成された要素を処理することができます。


おかげで-これは、子コンポーネントが存在する場合にうまく機能します
MAhsan '16

これは私にとって非常に役に立ちました。コンポーネント外のクリックが他の回答で検出されなかった理由がわかりません。
JavaQuest

13

iOSでこれを行う場合は、 touchstartイベントもします。

Angular 4以降、HostListenerデコレートはこれを行うための好ましい方法です

import { Component, OnInit, HostListener, ElementRef } from '@angular/core';
...
@Component({...})
export class MyComponent implement OnInit {

  constructor(private eRef: ElementRef){}

  @HostListener('document:click', ['$event'])
  @HostListener('document:touchstart', ['$event'])
  handleOutsideClick(event) {
    // Some kind of logic to exclude clicks in Component.
    // This example is borrowed Kamil's answer
    if (!this.eRef.nativeElement.contains(event.target) {
      doSomethingCool();
    }
  }

}

10

私たちは今日、同様の問題に取り組んでおり、ドロップダウンdivがクリックされたときにそれを非表示にする方法を理解しようとしています。別のコンポーネントディレクティブから離れてクリックするのではなく、特定のdivの外側にいるだけなので、最初の投稿者の質問とは少し異なります。

(window:mouseup)イベントハンドラーを使用して解決しました。

手順:
1.)ドロップダウンメニュー全体にdivに一意のクラス名を付けました。

2.)内部のドロップダウンメニュー自体(メニューを閉じないようにクリックしたい唯一の部分)に、(window:mouseup)イベントハンドラーを追加し、$ eventに渡しました。

注:これは親クリックハンドラーと競合するため、通常の「クリック」ハンドラーでは実行できませんでした。

3.)コントローラーで、クリックアウトイベントで呼び出されるメソッドを作成し、event.closest(ドキュメントはこちら)を使用して、クリックされたスポットがターゲットクラスのdiv内にあるかどうかを確認します。

 autoCloseForDropdownCars(event) {
        var target = event.target;
        if (!target.closest(".DropdownCars")) { 
            // do whatever you want here
        }
    }
 <div class="DropdownCars">
   <span (click)="toggleDropdown(dropdownTypes.Cars)" class="searchBarPlaceholder">Cars</span>
   <div class="criteriaDropdown" (window:mouseup)="autoCloseForDropdownCars($event)" *ngIf="isDropdownShown(dropdownTypes.Cars)">
   </div>
</div>


「window:mouseup」は、ホストデコレータ内で使用する必要があります。
Shivam

@ Shivam--「ホストデコレータ内で使用する必要がある」という意味がわかりません。さらに説明してもらえますか?ありがとう!
Paige Bolduc

つまり、「ウィンドウ」オブジェクトを直接使用する代わりに、コンポーネントデコレータの「ホスト」プロパティ/コンポーネントの「HostListener」デコレータを使用する必要があります。これは、angular 2で「ウィンドウ」または「ドキュメント」オブジェクトを操作するときの標準的な方法です
。– Shivam

2
ブラウザーの互換性に注意してください。現在のところ.closest() IE / Edgeではサポートされていません(caniuse
superjos

5

ドロップダウンに兄弟要素を作成して、画面全体を覆い隠して、クリックイベントをキャプチャするためだけに表示することができます。次に、その要素のクリックを検出し、クリックされたときにドロップダウンを閉じることができます。要素がシルクスクリーンのクラスであるとしましょう、ここにいくつかのスタイルがあります:

.silkscreen {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
}

z-indexは、ドロップダウン以外のすべての上に配置するのに十分な高さにする必要があります。この場合、私のドロップダウンはz-index 2になります。

他の回答は私にとってはいくつかのケースでうまくいきました。ただし、ドロップダウン内の要素を操作したときにドロップダウンが閉じて、それを望まない場合もありました。予想通り、イベントターゲットに従って、コンポーネントに含まれていない要素を動的に追加しました。混乱するような並べ替えではなく、シルクスクリーンの方法で試してみようと思いました。


5

回避策はありませんでした。私はちょうどドキュメントを添付しました:次のように私のトグル機能をクリックしてください:

    @指令({
      セレクター: '[appDropDown]'
    })
    エクスポートクラスDropdownDirectiveはOnInit {

      @HostBinding( 'class.open')isOpen:boolean;

      コンストラクター(プライベートelemRef:ElementRef){}

      ngOnInit():void {
        this.isOpen = false;
      }

      @HostListener( 'document:click'、['$ event'])
      @HostListener( 'document:touchstart'、['$ event'])
      toggle(event){
        if(this.elemRef.nativeElement.contains(event.target)){
          this.isOpen =!this.isOpen;
        } そうしないと {
          this.isOpen = false;
      }
    }

それで、私が私の指示の外にいるとき、私はドロップダウンを閉じます。


4
import { Component, HostListener } from '@angular/core';

@Component({
    selector: 'custom-dropdown',
    template: `
        <div class="custom-dropdown-container">
            Dropdown code here
        </div>
    `
})
export class CustomDropdownComponent {
    thisElementClicked: boolean = false;

    constructor() { }

    @HostListener('click', ['$event'])
    onLocalClick(event: Event) {
        this.thisElementClicked = true;
    }

    @HostListener('document:click', ['$event'])
    onClick(event: Event) {
        if (!this.thisElementClicked) {
            //click was outside the element, do stuff
        }
        this.thisElementClicked = false;
    }
}

ダウンサイド:-ページ上のこれらのコンポーネントのそれぞれに対して2つのクリックイベントリスナー。何百回もページにあるコンポーネントにはこれを使用しないでください。


いいえ、デスクトップブラウザでのみ使用しました。
Alex Egli 2017年

3

イベントはコンポーネントの外側をクリックした後に削除されないため、@ Tonyの回答を補足したいと思います。完全な領収書:

  • #containerでメイン要素をマーク

    @ViewChild('container') container;
    
    _dropstatus: boolean = false;
    get dropstatus() { return this._dropstatus; }
    set dropstatus(b: boolean) 
    {
        if (b) { document.addEventListener('click', this.offclickevent);}
        else { document.removeEventListener('click', this.offclickevent);}
        this._dropstatus = b;
    }
    offclickevent: any = ((evt:any) => { if (!this.container.nativeElement.contains(evt.target)) this.dropstatus= false; }).bind(this);
  • クリック可能な要素で、次を使用します。

    (click)="dropstatus=true"

これで、dropstatus変数を使用してドロップダウン状態を制御し、[ngClass]を使用して適切なクラスを適用できます...


3

ディレクティブを書くことができます:

@Directive({
  selector: '[clickOut]'
})
export class ClickOutDirective implements AfterViewInit {
  @Input() clickOut: boolean;

  @Output() clickOutEvent: EventEmitter<any> = new EventEmitter<any>();

  @HostListener('document:mousedown', ['$event']) onMouseDown(event: MouseEvent) {

       if (this.clickOut && 
         !event.path.includes(this._element.nativeElement))
       {
           this.clickOutEvent.emit();
       }
  } 


}

あなたのコンポーネントで:

@Component({
  selector: 'app-root',
  template: `
    <h1 *ngIf="isVisible" 
      [clickOut]="true" 
      (clickOutEvent)="onToggle()"
    >{{title}}</h1>
`,
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  title = 'app works!';

  isVisible = false;

  onToggle() {
    this.isVisible = !this.isVisible;
  }
}

このディレクティブは、HTML要素がDOMに含まれていて、[clickOut]入力プロパティが「true」の場合にイベントを発行します。要素がDOMから削除される前に、mousedownイベントをリッスンしてイベントを処理します。

そして1つの注意:関数を使用してパスを作成できるイベントでは、firefoxにプロパティ「path」が含まれていません。

const getEventPath = (event: Event): HTMLElement[] => {
  if (event['path']) {
    return event['path'];
  }
  if (event['composedPath']) {
    return event['composedPath']();
  }
  const path = [];
  let node = <HTMLElement>event.target;
  do {
    path.push(node);
  } while (node = node.parentElement);
  return path;
};

したがって、ディレクティブのイベントハンドラーを変更する必要があります。event.pathをgetEventPath(event)に置き換える必要があります。

このモジュールが役立ちます。https://www.npmjs.com/package/ngx-clickout 同じロジックが含まれていますが、ソースhtml要素のescイベントも処理します。


3

正しい答えには問題があります。ポップオーバーにクリカブルコンポーネントがある場合、要素はcontainメソッドからなくなり、@ JuHarm89に基づいて閉じます。

export class PopOverComponent implements AfterViewInit {
 private parentNode: any;

  constructor(
    private _element: ElementRef
  ) { }

  ngAfterViewInit(): void {
    this.parentNode = this._element.nativeElement.parentNode;
  }

  @HostListener('document:click', ['$event.path'])
  onClickOutside($event: Array<any>) {
    const elementRefInPath = $event.find(node => node === this.parentNode);
    if (!elementRefInPath) {
      this.closeEventEmmit.emit();
    }
  }
}

助けてくれてありがとう!


2

@Tonyの優れたソリューションのより良いバージョン:

@Component({})
class SomeComponent {
    @ViewChild('container') container;
    @ViewChild('dropdown') dropdown;

    constructor() {
        document.addEventListener('click', this.offClickHandler.bind(this)); // bind on doc
    }

    offClickHandler(event:any) {
        if (!this.container.nativeElement.contains(event.target)) { // check click origin

            this.dropdown.nativeElement.closest(".ourDropdown.open").classList.remove("open");

        }
    }
}

cssファイル://ブートストラップドロップダウンを使用する場合は不要。

.ourDropdown{
   display: none;
}
.ourDropdown.open{
   display: inherit;
}

2

モーダルオーバーレイをクリックするかどうかを確認する必要があります。

あなたのテンプレート:

<div #modalOverlay (click)="clickOutside($event)" class="modal fade show" role="dialog" style="display: block;">
        <div class="modal-dialog" [ngClass]='size' role="document">
            <div class="modal-content" id="modal-content">
                <div class="close-modal" (click)="closeModal()"> <i class="fa fa-times" aria-hidden="true"></i></div>
                <ng-content></ng-content>
            </div>
        </div>
    </div>

そして方法:

  @ViewChild('modalOverlay') modalOverlay: ElementRef;

// ... your constructor and other method

      clickOutside(event: Event) {
    const target = event.target || event.srcElement;
    console.log('click', target);
    console.log("outside???", this.modalOverlay.nativeElement == event.target)
    // const isClickOutside = !this.modalBody.nativeElement.contains(event.target);
    // console.log("click outside ?", isClickOutside);
    if ("isClickOutside") {
      // this.closeModal();
    }


  }

2

この同様の問題に対処するためのディレクティブを作成し、Bootstrapを使用しています。しかし、私の場合、要素の外側のクリックイベントが現在開いているドロップダウンメニューを閉じるのを待つ代わりに、 'mouseleave'イベントを監視してメニューを自動的に閉じる方が良いと思います。

これが私の解決策です:

指令

import { Directive, HostListener, HostBinding } from '@angular/core';
@Directive({
  selector: '[appDropdown]'
})
export class DropdownDirective {

  @HostBinding('class.open') isOpen = false;

  @HostListener('click') toggleOpen() {
    this.isOpen = !this.isOpen;
  }

  @HostListener('mouseleave') closeDropdown() {
    this.isOpen = false;
  }

}

HTML

<ul class="nav navbar-nav navbar-right">
    <li class="dropdown" appDropdown>
      <a class="dropdown-toggle" data-toggle="dropdown">Test <span class="caret"></span>
      </a>
      <ul class="dropdown-menu">
          <li routerLinkActive="active"><a routerLink="/test1">Test1</a></li>
          <li routerLinkActive="active"><a routerLink="/test2/">Test2</a></li>
      </ul>
    </li>
</ul>

1

Bootstrapを使用している場合は、ドロップダウン(Bootstrapコンポーネント)を介して直接ブートストラップすることができます。

<div class="input-group">
    <div class="input-group-btn">
        <button aria-expanded="false" aria-haspopup="true" class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">
            Toggle Drop Down. <span class="fa fa-sort-alpha-asc"></span>
        </button>
        <ul class="dropdown-menu">
            <li>List 1</li>
            <li>List 2</li>
            <li>List 3</li>
        </ul>
    </div>
</div>

(click)="clickButton()"ボタンに物を置いても大丈夫です。 http://getbootstrap.com/javascript/#dropdowns


1

私も少し自分の回避策を行いました。

ng-select要素コンポーネントでリッスンする(dropdownOpen)イベントを作成し、現在開いているSelectComponentを除いて、開いている他のすべてのSelectComponentを閉じる関数を呼び出します。

以下のように、select.tsファイル内の1つの関数を変更して、イベントを発行します。

private open():void {
    this.options = this.itemObjects
        .filter((option:SelectItem) => (this.multiple === false ||
        this.multiple === true && !this.active.find((o:SelectItem) => option.text === o.text)));

    if (this.options.length > 0) {
        this.behavior.first();
    }
    this.optionsOpened = true;
    this.dropdownOpened.emit(true);
}

HTMLで(dropdownOpened)のイベントリスナーを追加しました。

<ng-select #elem (dropdownOpened)="closeOtherElems(elem)"
    [multiple]="true"
    [items]="items"
    [disabled]="disabled"
    [isInputAllowed]="true"
    (data)="refreshValue($event)"
    (selected)="selected($event)"
    (removed)="removed($event)"
    placeholder="No city selected"></ng-select>

これは、ng2-selectタグを持つコンポーネント内のイベントトリガーでの私の呼び出し関数です:

@ViewChildren(SelectComponent) selectElem :QueryList<SelectComponent>;

public closeOtherElems(element){
    let a = this.selectElem.filter(function(el){
                return (el != element)
            });

    a.forEach(function(e:SelectComponent){
        e.closeDropdown();
    })
}

1

注: Webワーカーを使用する必要があり、documentとnativeElementの使用を回避する必要がある場合は、これが機能します。

私はここで同じ質問に答えました:https : //stackoverflow.com/questions/47571144

上記のリンクからコピー/貼り付け:

ドロップダウンメニューと確認ダイアログを作成しているときにも同じ問題が発生しました。

私の最終的な実装は完全に動作しますが、css3アニメーションとスタイリングが必要です。

注意:以下のコードはテストしていません。解決する必要のある構文の問題がある可能性があります。また、独自のプロジェクトの明らかな調整も含まれています。

私がしたこと:

高さ100%、幅100%、transform:scale(0)で個別の固定divを作成しました。これは基本的に背景です。背景色でスタイルを設定できます:rgba(0、0、0、0.466); メニューが開いており、背景がクリックして閉じるようになっています。メニューは他よりも高いz-indexを取得し、次に背景divはメニューよりも低いが他よりも高いz-indexを取得します。次に、背景には、ドロップダウンを閉じるクリックイベントがあります。

ここにあなたのhtmlコードがあります。

<div class="dropdownbackground" [ngClass]="{showbackground: qtydropdownOpened}" (click)="qtydropdownOpened = !qtydropdownOpened"><div>
<div class="zindex" [class.open]="qtydropdownOpened">
  <button (click)="qtydropdownOpened = !qtydropdownOpened" type="button" 
         data-toggle="dropdown" aria-haspopup="true" [attr.aria-expanded]="qtydropdownOpened ? 'true': 'false' ">
   {{selectedqty}}<span class="caret margin-left-1x "></span>
 </button>
  <div class="dropdown-wrp dropdown-menu">
  <ul class="default-dropdown">
      <li *ngFor="let quantity of quantities">
       <a (click)="qtydropdownOpened = !qtydropdownOpened;setQuantity(quantity)">{{quantity  }}</a>
       </li>
   </ul>
  </div>
 </div>

これは、簡単なアニメーションが必要なcss3です。

/* make sure the menu/drop-down is in front of the background */
.zindex{
    z-index: 3;
}

/* make background fill the whole page but sit behind the drop-down, then
scale it to 0 so its essentially gone from the page */
.dropdownbackground{
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 2;
    transform: scale(0);
    opacity: 0;
    background-color: rgba(0, 0, 0, 0.466);
}

/* this is the class we add in the template when the drop down is opened
it has the animation rules set these how you like */
.showbackground{
    animation: showBackGround 0.4s 1 forwards; 

}

/* this animates the background to fill the page
if you don't want any thing visual you could use a transition instead */
@keyframes showBackGround {
    1%{
        transform: scale(1);
        opacity: 0;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

視覚的なことを何もしていない場合は、このようなトランジションを使用できます

.dropdownbackground{
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 2;
    transform: scale(0);
    opacity: 0;
    transition all 0.1s;
}

.dropdownbackground.showbackground{
     transform: scale(1);
}

1

focus / blurイベントの例に触発されて、別のソリューションに出くわしました。

したがって、グローバルドキュメントリスナーをアタッチせずに同じ機能を実現したい場合は、次の例を有効と見なすことができます。ボタンフォーカスイベントの他の処理があるにもかかわらず、OSx上のSafariおよびFirefoxでも機能します。ます。https

角度8のstackbizの作業例: https

HTMLマークアップ:

<div class="dropdown">
  <button class="btn btn-secondary dropdown-toggle" type="button" aria-haspopup="true" aria-expanded="false">Dropdown button</button>
  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
    <a class="dropdown-item" href="#">Action</a>
    <a class="dropdown-item" href="#">Another action</a>
    <a class="dropdown-item" href="#">Something else here</a>
  </div>
</div>

ディレクティブは次のようになります。

import { Directive, HostBinding, ElementRef, OnDestroy, Renderer2 } from '@angular/core';

@Directive({
  selector: '.dropdown'
})
export class ToggleDropdownDirective {

  @HostBinding('class.show')
  public isOpen: boolean;

  private buttonMousedown: () => void;
  private buttonBlur: () => void;
  private navMousedown: () => void;
  private navClick: () => void;

  constructor(private element: ElementRef, private renderer: Renderer2) { }

  ngAfterViewInit() {
    const el = this.element.nativeElement;
    const btnElem = el.querySelector('.dropdown-toggle');
    const menuElem = el.querySelector('.dropdown-menu');

    this.buttonMousedown = this.renderer.listen(btnElem, 'mousedown', (evt) => {
      console.log('MOUSEDOWN BTN');
      this.isOpen = !this.isOpen;
      evt.preventDefault(); // prevents loose of focus (default behaviour) on some browsers
    });

    this.buttonMousedown = this.renderer.listen(btnElem, 'click', () => {
      console.log('CLICK BTN');
      // firefox OSx, Safari, Ie OSx, Mobile browsers.
      // Whether clicking on a <button> causes it to become focused varies by browser and OS.
      btnElem.focus();
    });

    // only for debug
    this.buttonMousedown = this.renderer.listen(btnElem, 'focus', () => {
      console.log('FOCUS BTN');
    });

    this.buttonBlur = this.renderer.listen(btnElem, 'blur', () => {
      console.log('BLUR BTN');
      this.isOpen = false;
    });

    this.navMousedown = this.renderer.listen(menuElem, 'mousedown', (evt) => {
      console.log('MOUSEDOWN MENU');
      evt.preventDefault(); // prevents nav element to get focus and button blur event to fire too early
    });
    this.navClick = this.renderer.listen(menuElem, 'click', () => {
      console.log('CLICK MENU');
      this.isOpen = false;
      btnElem.blur();
    });
  }

  ngOnDestroy() {
    this.buttonMousedown();
    this.buttonBlur();
    this.navMousedown();
    this.navClick();
  }
}

1

使用できます mouseleaveこのようにあなたのビューで

角度8でテストして完璧に動作する

<ul (mouseleave)="closeDropdown()"> </ul>

マウスが離れるとコンテナが閉じますが、とにかくコンテナの存在を知らなかったので、共有していただきありがとうございます。
ベンヘイワード

0

最もエレガントな方法:D

これを行う最も簡単な方法が1つあります。そのためのディレクティブは必要ありません。

「element-that-toggle-your-dropdown」はボタンタグにする必要があります。(ぼかし)属性で任意のメソッドを使用します。それで全部です。

<button class="element-that-toggle-your-dropdown"
               (blur)="isDropdownOpen = false"
               (click)="isDropdownOpen = !isDropdownOpen">
</button>

クリック時にドロップダウンを開いたままにしたい場合、これは機能しません。たとえば、ユーザーがボタンをクリックしない場合があります
はい、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.