Подтвердить что ты не робот

Как закрыть раскрывающийся список при нажатии на кнопку снаружи?

Я хотел бы закрыть раскрывающееся меню входа в систему, когда пользователь щелкнет где-нибудь за пределами этого раскрывающегося списка, и я хотел бы сделать это с помощью Angular2 и с помощью Angular2 "...

Я реализовал решение, но я действительно не уверен в этом. Я думаю, что должен быть самый простой способ добиться того же результата, поэтому, если у вас есть какие-то идеи... пусть обсуждают:)!

Вот моя реализация:

Выпадающий компонент:

Это компонент для моего раскрывающегося списка:

  • Каждый раз, когда этот компонент устанавливается на видимый (например: когда пользователь нажимает на кнопку для его отображения), он подписывается на "глобальный" пользовательский пользовательский интерфейс rxjs, сохраненный в рамках SubjectsService.
  • И каждый раз, когда он скрыт, он отказывается от подписки на эту тему.
  • Каждый клик в любом месте внутри, шаблон этого компонента запускает метод 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;
    }
}

Компонент приложения:

С другой стороны, есть компонент приложения (который является родителем компонента выпадающего списка):

  • Этот компонент улавливает каждое событие click и испускает тот же объект rxjs Subject (userMenu)

Вот код:

export class AppComponent {

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

Что меня беспокоит:

  • Мне не очень нравится идея создания глобального объекта, который выступает в качестве соединителя между этими компонентами.
  • setTimeout: это необходимо, потому что вот что происходит иначе, если пользователь нажимает кнопку, показывающую выпадающий список:
    • Пользователь нажимает кнопку (которая не входит в компонент раскрывающегося списка), чтобы отобразить раскрывающееся меню.
    • Отображается выпадающий список и немедленно подписывается на тему userMenu.
    • Вызов пузырька события до компонента приложения и попадается
    • Компонент приложения испускает событие в теме userMenu
    • Выпадающий компонент поймает это действие на userMenu и скроет раскрывающееся меню.
    • В конце раскрывающийся список никогда не отображается.

Этот заданный тайм-аут задерживает подписку до конца текущего кода JavaScript-кода, который решает проблему, но очень изящным способом, на мой взгляд.

Если вы знаете более чистые, лучшие, умные, быстрые или сильные решения, сообщите мне:!

4b9b3361

Ответ 1

Вы можете использовать событие (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();
  }
}

Другим подходом является создание настраиваемого события в качестве директивы. Ознакомьтесь с этими сообщениями Бен Надел:

Ответ 2

ЭЛЕГАНТНЫЙ МЕТОД

Я нашел эту директиву clickOut: https://github.com/chliebel/angular2-click-outside. Я проверяю это, и он работает хорошо (я только clickOutside.directive.ts в мой проект). Вы можете использовать это следующим образом:

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

Где close ваша функция, которая будет вызываться, когда пользователь нажимает за пределами div. Это очень элегантный способ справиться с проблемой, описанной в вопросе.

Если вы используете указанную выше директиву для закрытия всплывающего окна, не забудьте сначала добавить event.stopPropagation() к обработчику события нажатия кнопки, который открывает всплывающее окно.

БОНУС:

Ниже я clickOutside.directive.ts код директивы из файла clickOutside.directive.ts (в случае, если ссылка перестанет работать в будущем) - автор Кристиан Либель:

import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
    
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef: ElementRef) {
    }

    @Output()
    public clickOutside = new EventEmitter<MouseEvent>();

    @HostListener('document:click', ['$event', '$event.target'])
    public onClick(event: MouseEvent, targetElement: HTMLElement): void {
        if (!targetElement) {
            return;
        }

        const clickedInside = this._elementRef.nativeElement.contains(targetElement);
        if (!clickedInside) {
            this.clickOutside.emit(event);
        }
    }
}

Ответ 3

Я сделал это так.

Добавлен прослушиватель событий в документе click и в том, что обработчик проверял, содержит ли мой 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";
        }
    }
}

Ответ 4

Я думаю, что Саша принял ответные работы для большинства людей. Однако у меня была ситуация, когда содержимое элемента, которое должно прослушивать события с щелчком, менялось динамически. Таким образом, элемент 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, я проверяю, находится ли элементRef в пути (путь DOM к цели) события. Таким образом, можно обрабатывать динамически созданные элементы.

Ответ 5

Если вы делаете это на 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 answer
    if (!this.eRef.nativeElement.contains(event.target) {
      doSomethingCool();
    }
  }

}

Ответ 6

Мы работаем над подобной проблемой на работе сегодня, пытаясь выяснить, как заставить выпадающий div исчезнуть, когда его вызывают. Наши немного отличаются от первоначального вопроса о посте, потому что мы не хотели нажимать на другой компонент или директиву, но просто вне определенного div.

Мы закончили его решение, используя обработчик события (window: mouseup).

Шаги:
1.) Мы дали всему выпадающему меню div уникальное имя класса.

2.) Во внутреннем выпадающем меню (единственное, что мы хотели, чтобы клики НЕ закрывали меню), мы добавили обработчик события (window: mouseup) и передали в $event. ПРИМЕЧАНИЕ. Это не может быть сделано с помощью обычного обработчика "click", поскольку это противоречит родительскому обработчику кликов.

3.) В нашем контроллере мы создали метод, который мы хотели бы назвать в событии clickout, и мы используем event.closest(docs here), чтобы узнать, находится ли нажатое пятно внутри нашего целевого класса 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>

Ответ 7

Я не делал обходного пути. Я только что приложил документ: нажмите на мою функцию переключения, как показано ниже:

    @Directive({
      selector: '[appDropDown]'
    })
    export class DropdownDirective implements OnInit {

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

      constructor(private 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;
        } else {
          this.isOpen = false;
      }
    }

Итак, когда я наружу свою директиву, я закрываю выпадающее меню.

Ответ 8

В раскрывающемся списке вы можете создать элемент sibling, который будет охватывать весь экран, который будет невидимым, и быть там только для захвата событий щелчка. Затем вы можете обнаружить клики по этому элементу и закрыть раскрывающееся меню, когда оно будет нажато. Допустим, что элемент имеет шелкографию класса, вот какой стиль для него:

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

Z-индекс должен быть достаточно высоким, чтобы разместить его выше всего, кроме раскрывающегося списка. В этом случае мое раскрывающееся меню будет b z-index 2.

Другие ответы работали в некоторых случаях для меня, за исключением того, что иногда мое раскрывающееся меню закрывалось, когда я взаимодействовал с элементами внутри него, и я этого не хотел. Я добавил динамически добавленные элементы, которые не содержались в моем компоненте, в соответствии с целевым событием, как я и ожидал. Вместо того, чтобы сортировать этот беспорядок, я решил, что просто попробую сделать это шелкографическим способом.

Ответ 9

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;
    }
}

МИНУСЫ: - Два прослушивателя событий для каждого из этих компонентов на странице. Не используйте это для компонентов, которые находятся на странице сотни раз.

Ответ 10

Я хотел бы дополнить ответ @Tony, поскольку событие не удаляется после щелчка вне компонента. Полная квитанция:

  • Отметьте свой основной элемент # контейнером

    @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]...

Ответ 11

Правильный ответ имеет проблему, если у вас есть компонент clicakble в вашем popover, этот элемент больше не будет использоваться для метода 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();
    }
  }
}

Спасибо за помощь!

Ответ 12

Лучшая версия для отличного решения @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;
}

Ответ 13

Вы можете написать директиву:

@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] является "истинным". Он прослушивает событие mousedown для обработки события, прежде чем элемент будет удален из DOM.

И одно примечание: firefox не содержит свойства "путь" в событии, вы можете использовать функцию для создания пути:

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 (событие)

Этот модуль может помочь. https://www.npmjs.com/package/ngx-clickout Он содержит ту же логику, но также обрабатывает событие esc на элементе source html.

Ответ 14

Вы должны проверить, не нажмете ли вы на модальный оверлей, намного проще.

Ваш шаблон:

<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();
    }


  }

Ответ 15

Если вы используете 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

Ответ 16

Я также сделал небольшое обходное решение.

Я создал событие (dropdownOpen), которое я слушаю в своем элементе элемента ng-select и вызываю функцию, которая закрывает все остальные SelectComponent, открытые отдельно от открытого в данный момент SelectComponent.

Я изменил одну функцию внутри файла select.ts, как показано ниже, чтобы выпустить событие:

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();
    })
}

Ответ 17

ПРИМЕЧАНИЕ: для тех, кто хочет использовать веб-работников, и вам нужно избегать использования документа и nativeElement, это будет работать.

Я ответил на тот же вопрос здесь: fooobar.com/questions/13623793/...

Скопируйте/Вставьте по ссылке выше:

У меня была такая же проблема, когда я делал раскрывающееся меню и диалоговое окно подтверждения, я хотел отклонить их при нажатии снаружи.

Моя финальная реализация работает отлично, но требует анимации и стиля CSS3.

ПРИМЕЧАНИЕ: я не тестировал приведенный ниже код, могут быть некоторые проблемы с синтаксисом, которые необходимо устранить, а также очевидные корректировки для вашего собственного проекта!

Что я сделал:

Я сделал отдельный фиксированный div с высотой 100%, шириной 100% и трансформировал: scale (0), это по сути фон, вы можете стилизовать его с background-color: rgba (0, 0, 0, 0.466); чтобы было понятно, что меню открыто, а фон закрывается. Меню получает z-индекс выше, чем все остальное, затем фоновый div получает z-индекс ниже, чем меню, но также выше, чем все остальное. Затем фон имеет событие щелчка, которое закрывает раскрывающийся список.

Вот это с вашим 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);
}

Ответ 18

Я сделал директиву для решения этой аналогичной проблемы, и я использую Bootstrap. Но в моем случае вместо ожидания события click за пределами элемента, чтобы закрыть текущее открытое выпадающее меню, я думаю, что лучше, если мы проследим за событием 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>

Ответ 19

САМЫЙ ЭЛЕГАНТНЫЙ МЕТОД: D

Есть один самый простой способ сделать это, не нужно никаких указаний для этого.

"element-that-toggle-your-dropdown" должен быть тегом кнопки. Используйте любой метод в (размытие) атрибута. Все это.

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

Ответ 20

Я наткнулся на другое решение, вдохновленное примерами с фокусом/размытием.

Таким образом, если вы хотите достичь той же функциональности без подключения глобального прослушивателя документов, вы можете считать допустимым следующий пример. Это работает также в Safari и Firefox на OSx, несмотря на то, что они имеют другую обработку события фокуса кнопки:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Рабочий пример на стекбизнесе с угловым 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts

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();
  }
}