Як я можу закрити спадне меню при натисканні зовні?


144

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

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

Ось моя реалізація:

Спадаючий компонент:

Це компонент для мого спаду:

  • Кожен раз, коли цей компонент встановлюється видимим (наприклад: коли користувач натискає кнопку, щоб відобразити його), він підписується на "глобальну" тему rxjs userMenu, що зберігається в 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;
    }
}

Компонент програми:

З іншого боку, є компонент програми (який є батьківським компонентом випадаючого компонента):

  • Цей компонент вловлює кожну подію клацання та випромінює в одному темі 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/215945 , stackoverflow.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

4
Якщо event.target - це елемент, який динамічно додається через щось на зразок [InternalHTML] прив'язки, то nativeElement елементаRef не буде містити його.
Патрік Грем

8
Єдиним недоліком цієї методики є те, що тепер у вашому додатку є слухач подій клацання, який спрацьовує кожного разу при натисканні.
codeepic

37
Згідно з офіційним посібником зі стилю Angular 2, слід використовувати @HostListener('document:click', ['$event'])замість hostвласності Componentдекоратор.
Michał Miszczyszyn

15
або ви можете просто використовувати rxjs для нього, як-от Observable.fromEvent(document, 'click').subscribe(event => {your code here}), тому ви завжди можете підписатися лише тоді, коли вам потрібно прослухати, наприклад, коли ви відкрили спадне меню, а коли закриєте його, ви скасуєте підписку
Blind Despair

43

ЕЛЕГАНТНИЙ МЕТОД

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

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

Де closeваша функція, яка буде дзвонити, коли користувач клацне за межами div. Це дуже елегантний спосіб вирішити проблему, описану в питанні.

Якщо ви використовуєте вищезазначену директиву, щоб закрити popUp вікно, не забудьте спочатку додати event.stopPropagation()до кнопки обробник подій, який відкриє popUp.

БОНУС:

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


2
@Vega Моїм рекомендацією було б використання Директиви в елементі з * ngIf, у випадку спадання, це може бути щось на кшталт<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Габріель Бальса Канту,

19

Я зробив це так.

Додано слухача події в документ 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";
        }
    }
}

Привіт. Чи потрібна .bind (це)?
Дренай

1
@Brian Це може бути, а може і не знадобитися, але, безумовно, це не буде, якби він завернув this.offClickHandlerфункцію зі стрілкою.
Lansana Camara

17

Я думаю, що Сакса прийняла відповідь працює для більшості людей. Однак у мене виникла ситуація, коли вміст Елементу, який повинен слухати події, що не клацали, динамічно змінювався. Таким чином, Елементи nativeElement не містили target.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

Це мені дуже допомогло. не впевнений, чому за межами компонента клацання не було виявлено за допомогою інших відповідей.
JavaQuest

13

Якщо ви робите це на iOS, використовуйте touchstart подію:

Щодо кутового 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

Ми працюємо над подібною проблемою на роботі сьогодні, намагаючись з'ясувати, як змусити зникнути спадний поділ при його натисканні. Наше дещо відрізняється від початкового питання про афішу, оскільки ми не хотіли відштовхуватися від іншого компонента чи директиви , а лише поза окремим ділом.

Ми вирішили це за допомогою обробника подій (window: mouseup).

Етапи:
1.) Ми надали всій розкривному меню div унікальну назву класу.

2.) У самому внутрішньому спадному меню (єдиній частині, яку ми хотіли, щоб кліки НЕ закрили меню), ми додали обробник подій (window: mouseup) та передали в $ події.

ПРИМІТКА. Це неможливо зробити за допомогою типового обробника "натискання", оскільки це суперечило батьківському обробнику кліків.

3.) У нашому контролері ми створили метод, про який хотіли, щоб нас викликали події натискання, і ми використовуємо event.closest ( документи тут ), щоб дізнатись, чи натиснута пляма в межах нашого цільового класу поділу.

 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 - я не впевнений, що ви маєте на увазі під "поняттям", яке потрібно використовувати в декораторі хоста ". Не могли б ви пояснити далі? Дякую!
Пейдж Болдук

Я маю на увазі, замість того, щоб безпосередньо використовувати об’єкт "window", слід використовувати властивість "host" компонента decorator / "HostListener" декоратора компонента. Це стандартна практика під час роботи з об'єктом "вікно" або "документ" у кутку 2.
Шивам,

2
Просто слідкуйте за сумісністю браузера, .closest()не підтримується IE / Edge на сьогодні ( канюза )
superjos

5

Ви можете створити елемент випуску з випадаючого меню, який охоплює весь екран, який був би непомітним і був там лише для фіксації подій клацання. Тоді ви могли виявити кліки на цьому елементі та закрити спадне меню при натисканні на нього. Скажемо, що цей елемент є шовковим екраном класу, ось його стиль:

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

Z-індекс повинен бути достатньо високим, щоб розташувати його над усім, крім випадаючого. У цьому випадку моє спадне місце буде b z-індексом 2.

Інші відповіді спрацювали в деяких випадках для мене, за винятком випадків, коли випадання закривалося, коли я взаємодіяв з елементами всередині нього, і я цього не хотів. У мене були динамічно додані елементи, які не містилися в моєму компоненті, відповідно до цілі події, як я очікував. Замість того, щоб розбирати цей безлад, я подумав, що я просто спробую це на шовковій екрані.


5

Я не замислювався. Я щойно додав документ: натисніть на свою функцію перемикання так:

    @Directive ({
      селектор: "[appDropDown]"
    })
    експортний клас DropdownDirective реалізує OnInit {

      @HostBinding ('class.open') isOpen: булева;

      конструктор (приватний elemRef: ElementRef) {}

      ngOnInit (): void {
        this.isOpen = хибно;
      }

      @HostListener ("документ: натисніть", ['$ подія'])
      @HostListener ('документ: touchstart', ['$ event'])
      перемикач (подія) {
        якщо (this.elemRef.nativeElement.contains (event.target)) {
          this.isOpen =! this.isOpen;
        } else {
          this.isOpen = хибно;
      }
    }

Отже, коли я перебуваю за межами своєї директиви, закриваю спадне меню.


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

ДОСЛІДЖЕННЯ: - Два слухачі подій, що клацають по кожному з цих компонентів на сторінці. Не використовуйте це для компонентів, які є на сторінці сотні разів.


Ні, я використовував його лише в настільному браузері.
Алекс Еглі

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". Він слухає подію mousedown для обробки події до того, як елемент буде видалений з DOM.

І одне зауваження: 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 Він містить ту саму логіку, але також обробляє esc подію на вихідний html елемент.


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

Я також трохи зробив своє власне рішення.

Я створив подію (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();
    })
}

1

ПРИМІТКА. Для тих, хто хоче користуватися веб-працівниками, і вам потрібно уникати використання документів та nativeElement, це спрацює.

Я відповів на те саме запитання тут: /programming/47571144

Скопіюйте / Вставте з вищевказаного посилання:

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

Моя остаточна реалізація працює чудово, але вимагає анімації та стилів css3.

ПРИМІТКА : я не перевіряв наведений нижче код, можуть виникнути деякі проблеми з синтаксисом, які потрібно випрасувати, а також очевидні корективи для вашого власного проекту!

Що я зробив:

Я зробив окремий фіксований div з висотою 100%, шириною 100% і перетворенням: масштаб (0), це по суті фон, ви можете його стилювати за допомогою фонового кольору: 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);
}

1

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

Отже, якщо ви хочете досягти тієї ж функціональності, не приєднуючи глобального слухача документа, ви можете вважати наступним прикладом дійсним. Він також працює в 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();
  }
}

1

Ви можете використовувати mouseleaveу своєму поданні так

Тестуйте з кутовим 8 і працюйте ідеально

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

Це закриє контейнер, коли миша піде, але дякую за те, що все одно поділився, оскільки я не знав про його існування.
Бен Хейвард

0

НАЙБІЛЬШИЙ МЕТОД: D

Є один найпростіший спосіб зробити це, для цього не потрібно ніяких директив.

"element-that-toggle-your-dropdown" повинен бути тегом кнопки. Використовуйте будь-який метод у (blur) атрибуті. Це все.

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

Це не спрацює, якщо ви хочете, щоб спадне меню було відкритим при натисканні, наприклад, користувач може пропустити натискання кнопки
Yeswhen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.