wake-up-neo.com

Wie kann ich ein Dropdown bei Klick außerhalb schließen?

Ich möchte mein Dropdown-Menü für das Anmeldemenü schließen, wenn der Benutzer auf eine beliebige Stelle außerhalb des Dropdown-Menüs klickt. Ich möchte dies mit Angular2 und Angular2 "Annäherung" tun ...

Ich habe eine Lösung implementiert, fühle mich aber wirklich nicht zuversichtlich. Ich denke, es muss einen einfachsten Weg geben, um das gleiche Ergebnis zu erzielen, also, wenn Sie irgendwelche Ideen haben ... lass uns darüber reden :)!

Hier ist meine Implementierung:

Die Dropdown-Komponente:

Dies ist die Komponente für mein Dropdown:

  • Jedes Mal, wenn diese Komponente sichtbar ist (zum Beispiel: Wenn der Benutzer auf eine Schaltfläche klickt, um sie anzuzeigen), abonniert er ein globales Rxj-Subjekt userMenu, das im SubjectsService gespeichert ist.
  • Und jedes Mal, wenn es verborgen ist, kündigt es dieses Thema an.
  • Jeder Klick irgendwo innerhalb der Vorlage dieser Komponente löst die onClick () -Methode aus, die das Blubbern von Ereignissen (und die Anwendungskomponente) aufhört.

Hier ist der Code

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

Die Anwendungskomponente:

Auf der anderen Seite gibt es die Anwendungskomponente (die der Dropdown-Komponente übergeordnet ist):

  • Diese Komponente erfasst jedes Klickereignis und gibt dasselbe rxjs-Betreff aus (userMenu).

Hier ist der Code:

export class AppComponent {

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

Was mich stört:

  1. Ich fühle mich nicht wirklich wohl mit der Idee, ein globales Subjekt zu haben, das als Verbindungsglied zwischen diesen Komponenten dient.
  2. Der setTimeout : Dies ist erforderlich, da hier Folgendes passiert, wenn der Benutzer auf die Schaltfläche klickt, die die Dropdown-Liste anzeigt:
    • Der Benutzer klickt auf die Schaltfläche (die nicht Teil der Dropdown-Komponente ist), um die Dropdown-Liste anzuzeigen.
    • Das Dropdown-Menü wird angezeigt und es abonniert sofort den Betreff userMenu .
    • Die Klickereignisblase reicht bis zur App-Komponente und wird dabei erwischt
    • Die Anwendungskomponente gibt ein Ereignis für das userMenu subject aus
    • Die Dropdown-Komponente fängt diese Aktion in userMenu ab und blendet die Dropdown-Liste aus.
    • Am Ende wird das Dropdown nie angezeigt.

Dieses gesetzte Timeout verzögert das Abonnement bis zum Ende des aktuellen JavaScript-Code-Zuges, wodurch das Problem gelöst wird, meiner Meinung nach jedoch auf sehr elegante Weise.

Wenn Sie saubere, bessere, intelligentere, schnellere oder stärkere Lösungen kennen, lass es mich wissen :)!

116
Clement

Sie können (document:click) event verwenden:

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

Ein anderer Ansatz besteht darin, ein benutzerdefiniertes Ereignis als Direktive zu erstellen. Schauen Sie sich diese Beiträge von Ben Nadel an:

204
Sasxa

ELEGANTE METHODE

Ich habe diese clickOut-Direktive gefunden: https://github.com/chliebel/angular2-click-outside . Ich überprüfe es und es funktioniert gut (ich kopiere nur clickOutside.directive.ts in mein Projekt). Sie können es auf diese Weise verwenden:

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

Dabei ist close Ihre Funktion, die aufgerufen wird, wenn der Benutzer außerhalb von div klickt. Es ist ein sehr eleganter Weg, um das in Frage stehende Problem zu behandeln.

Wenn Sie die obige Anweisung zum Schließen des PopUp-Fensters verwenden, denken Sie daran, zuerst event.stopPropagation() zum Ereignis-Handler der Schaltfläche click hinzuzufügen, der das PopUp-Fenster öffnet.

BONUS:

Nachfolgend kopiere ich den oryginal Direktive-Code aus der Datei clickOutside.directive.ts (falls der Link in Zukunft nicht mehr funktioniert) - Der Autor ist Christian Liebel :

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

Ich habe es so gemacht.

Ein Ereignis-Listener für Dokument click wurde hinzugefügt. In diesem Handler wurde geprüft, ob meine containerevent.target enthält. Andernfalls wird die Dropdownliste ausgeblendet.

Es würde so aussehen.

@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";
        }
    }
}
17
Tony

Ich denke, Sasxa akzeptierte Antwortarbeiten für die meisten Menschen. Ich hatte jedoch eine Situation, in der sich der Inhalt des Elements, der Off-Click-Ereignisse abhören soll, dynamisch geändert hat. Das native Element von Elements enthielt also nicht das event.target, wenn es dynamisch erstellt wurde ... Ich konnte dies mit der folgenden Direktive lösen

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

Anstatt zu prüfen, ob elementRef event.target enthält, prüfe ich, ob elementRef im Pfad (DOM-Pfad zum Ziel) des Ereignisses enthalten ist. Auf diese Weise ist es möglich, dynamisch erzeugte Elemente zu behandeln.

14
JuHarm89

Wir haben an einem ähnlichen Problem heute gearbeitet und versucht herauszufinden, wie ein Dropdown-Div ausgeblendet werden kann, wenn er angeklickt wird. Unsere unterscheidet sich ein wenig von der Frage des ursprünglichen Posters, da wir nicht von einer anderen -Komponente oder Direktive weg klicken wollten, sondern lediglich außerhalb des jeweiligen divs.

Am Ende haben wir es mit dem Event-Handler (window: mouseup) gelöst.

Schritte:
1.) Wir haben dem gesamten Dropdown-Menü div einen eindeutigen Klassennamen gegeben.

2.) Im inneren Dropdown-Menü selbst (der einzige Bereich, in dem wir wollten, dass das Menü NICHT geschlossen wird), haben wir einen (Fenster: Mouseup) -Ereignishandler hinzugefügt und das $ -Ereignis übergeben. 

HINWEIS: Dies kann nicht mit einem typischen "click" -Handler durchgeführt werden, da dies mit dem übergeordneten Click-Handler in Konflikt steht.

3.) In unserem Controller haben wir die Methode erstellt, die wir für das Click-Out-Ereignis aufrufen wollten, und wir verwenden event.closest ( docs here ), um herauszufinden, ob sich die angeklickte Stelle innerhalb befindet Unser Targeted-Class-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>

8
Paige Bolduc

Wenn Sie dies unter iOS tun, verwenden Sie auch das Ereignis touchstart:

Ab Angular 4 ist das Dekor HostListener der bevorzugte Weg, dies zu tun

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

}
8
Xavier

Sie können ein gleichgeordnetes Element für die Dropdown-Liste erstellen, das den gesamten Bildschirm abdeckt, der unsichtbar wäre, und nur für die Erfassung von Klickereignissen vorhanden sein. Dann könnten Sie Klicks auf dieses Element erkennen und die Dropdown-Liste schließen, wenn Sie darauf klicken. Nehmen wir an, das Element ist aus der Klasse Siebdruck, hier ist ein Stil dafür: 

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

Der Z-Index muss hoch genug sein, um ihn über alles außer Ihrem Dropdown zu positionieren. In diesem Fall wäre mein Dropdown-Index 2.

Die anderen Antworten funktionierten in einigen Fällen für mich, außer dass mein Dropdown manchmal geschlossen wurde, als ich mit Elementen darin interagierte und dies nicht wollte. Ich hatte dynamisch Elemente hinzugefügt, die nicht in meiner Komponente enthalten waren, je nach Ereignisziel wie erwartet. Anstatt das Chaos auszusortieren, dachte ich, ich probiere es einfach mit dem Siebdruck.

4
Patrick Graham

Ich habe keine Problemumgehung gemacht. Ich habe gerade ein Dokument angehängt: Klicken Sie wie folgt auf meine Umschaltfunktion:

 @Direktive ({
 Selector: '[appDropDown]' 
}) 
 Exportklasse DropdownDirective implementiert OnInit {

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

 Konstruktor (privates elemRef: ElementRef) {} 

 ngOnInit (): ungültig {
 this.isOpen = false; 
 } 

 @ HostListener ('document: click', ['$ event']) 
 @HostListener ('document: touchstart', ['$ event']) 
 Toggle (Ereignis) {
 if (this.elemRef.nativeElement.contains (event.target)) {
 this.isOpen =! this.isOpen; 
 } else {
 this.isOpen = false; 
 } 
 } 

Wenn ich also meine Direktive nicht betrete, schließe ich die Dropdown-Liste.

3
Elie Nehmé

Ich möchte die Antwort von @Tony ergänzen, da das Ereignis nach dem Klick außerhalb der Komponente nicht entfernt wird. Vollständige Quittung:

  • Markieren Sie Ihr Hauptelement mit #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);
    
  • Verwenden Sie für das anklickbare Element Folgendes:

    (click)="dropstatus=true"
    

Jetzt können Sie Ihren Dropdown-Status mit der Dropstatus-Variablen steuern und mit [ngClass] geeignete Klassen anwenden ...

3
Gauss

Die richtige Antwort hat ein Problem. Wenn Sie eine clicakble-Komponente in Ihrem Popover haben, wird das Element nicht mehr auf die contain-Methode angewendet und wird geschlossen. Basierend auf @ JuHarm89 habe ich meine eigene erstellt: 

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

Danke für die Hilfe!

2
Douglas Caina
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;
    }
}

DOWNSIDES: - Zwei Klickereignis-Listener für jede dieser Komponenten auf der Seite. Verwenden Sie dies nicht für Komponenten, die hunderte Male auf der Seite stehen.

2
Alex Egli

Sie können Direktive schreiben:

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


}

In Ihrer Komponente:

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

Diese Direktive gibt ein Ereignis aus, wenn das HTML-Element in DOM enthalten ist und die [clickOut] -Eingabeeigenschaft 'true' ist .. _. Sie überwacht ein Mousedown-Ereignis, um das Ereignis zu behandeln, bevor das Element aus DOM entfernt wird.

Und noch ein Hinweis: Firefox enthält bei Ereignissen nicht die Eigenschaft "path", mit der Sie die Funktion zum Erstellen eines Pfads verwenden können:

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

Sie sollten also den Ereignishandler in der Direktive ändern: event.path sollte getEventPath (event) ersetzt werden 

Dieses Modul kann helfen. https://www.npmjs.com/package/ngx-clickout Es enthält dieselbe Logik, behandelt jedoch auch das esc-Ereignis im Quell-HTML-Element.

1
Alex Mikitevich

Wenn Sie Bootstrap verwenden, können Sie dies direkt mit Bootstrap Way über Dropdowns (Bootstrap-Komponente) tun.

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

Jetzt können Sie (click)="clickButton()"-Sachen auf die Schaltfläche setzen . http://getbootstrap.com/javascript/#dropdowns

1
vusan

Sie sollten prüfen, ob Sie stattdessen auf das modale Overlay klicken.

Ihre Vorlage:

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

Und die Methode: 

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


  }
1
Stefdelec

Eine bessere Version für die großartige Lösung von @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");

        }
    }
}

In einer css-Datei: // NICHT erforderlich, wenn Sie das Bootstrap-Dropdown verwenden.

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

Ich habe auch einen kleinen Workaround von mir selbst gemacht.

Ich habe ein(dropdownOpen)-Ereignis erstellt, das ich an meiner ng-select-Elementkomponente abhöre, und rufe eine Funktion auf, mit der alle anderen SelectComponent-Fenster geschlossen werden, abgesehen von der gerade geöffneten SelectComponent.

Ich habe eine Funktion in der Dateiselect.tswie folgt geändert, um das Ereignis auszulösen:

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

Im HTML habe ich einen Event Listener für (dropdownOpened) hinzugefügt:

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

Dies ist meine aufrufende Funktion für den Ereignisauslöser in der Komponente mit dem Tag 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();
    })
}
0
Gaurav Pandvia

Ich habe eine Anweisung erlassen, um dieses ähnliche Problem anzugehen, und ich verwende Bootstrap. In meinem Fall halte ich es jedoch für besser, nicht auf das Click-Ereignis außerhalb des Elements zu warten, um das aktuell geöffnete Dropdown-Menü zu schließen. Ich denke, es ist besser, wenn wir das 'Mouseleave'-Ereignis überwachen, um das Menü automatisch zu schließen. 

Hier ist meine Lösung:

Richtlinie

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>
0

Ich bin auf eine andere Lösung gestoßen, die von Beispielen mit Fokus/Unschärfe-Ereignissen inspiriert wurde.

Wenn Sie also die gleiche Funktionalität erzielen möchten, ohne einen globalen Dokumentlistener anzuhängen, können Sie das folgende Beispiel als gültig betrachten. Es funktioniert auch in Safari und Firefox unter OSx, obwohl das Ereignis "Schaltflächenfokus" anders behandelt wird: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Arbeitsbeispiel für stackbiz mit angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts

HTML-Markup:

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

Die Richtlinie sieht folgendermaßen aus:

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

DIE ELEGANTESTE METHODE: D

Es gibt eine einfachste Möglichkeit, dies zu tun, ohne dass dafür Richtlinien erforderlich sind.

"Element, mit dem Sie die Dropdown-Liste wechseln können" sollte ein Schaltflächentag sein. Verwenden Sie eine beliebige Methode im Attribut (Unschärfe). Das ist alles.

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

NOTE: Für diejenigen, die Web Worker verwenden möchten und Sie die Verwendung von document und nativeElement vermeiden möchten, wird dies funktionieren. 

Ich habe die gleiche Frage hier beantwortet: https://stackoverflow.com/questions/47571144

Kopieren/Einfügen aus dem obigen Link:

Ich hatte das gleiche Problem, als ich ein Dropdown-Menü erstellte, und einen Bestätigungsdialog, den ich beim Klicken von außen verwerfen wollte.

Meine endgültige Implementierung funktioniert einwandfrei, erfordert jedoch einige css3-Animationen und -Styling. 

NOTE: Ich habe den folgenden Code nicht getestet. Möglicherweise müssen einige Syntaxprobleme behoben werden, auch die offensichtlichen Anpassungen für Ihr eigenes Projekt!

Was ich getan habe:

Ich habe ein separates festes div mit einer Höhe von 100%, einer Breite von 100% und einer Transformation erstellt: scale (0). Dies ist im Wesentlichen der Hintergrund. Sie können ihn mit Hintergrundfarbe gestalten: rgba (0, 0, 0, 0.466); um es offensichtlich zu machen, ist das Menü geöffnet und der Hintergrund ist Click-to-Close. Das Menü erhält einen Z-Index, der höher ist als alles andere, und der Hintergrund div erhält einen Z-Index, der niedriger als das Menü ist, aber auch höher als alles sonst. Dann hat der Hintergrund ein Klickereignis, das die Dropdown-Liste schließt.

Hier ist es mit Ihrem HTML-Code.

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

Hier ist die css3, die einige einfache Animationen benötigt.

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

Wenn Sie nichts Sichtbares suchen, können Sie einfach einen Übergang wie diesen verwenden

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

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