Come posso chiudere un menu a discesa facendo clic all'esterno?


144

Vorrei chiudere il menu a discesa del menu di accesso quando l'utente fa clic in un punto qualsiasi al di fuori di tale menu a discesa e mi piacerebbe farlo con Angular2 e con l'approccio "Angular2" ...

Ho implementato una soluzione, ma in realtà non mi sento sicuro. Penso che ci debba essere un modo più semplice per ottenere lo stesso risultato, quindi se hai qualche idea ... discutiamo :)!

Ecco la mia implementazione:

Il componente a discesa:

Questo è il componente per il mio menu a discesa:

  • Ogni volta che questo componente è impostato su visibile, (per esempio: quando l'utente fa clic su un pulsante per visualizzare esso) è la sottoscrizione di un "globale" rxjs soggetto usermenu memorizzati all'interno del SubjectsService .
  • E ogni volta che è nascosto, annulla l'iscrizione a questo argomento.
  • Ogni clic in qualsiasi punto all'interno del modello di questo componente attiva il metodo onClick () , che interrompe appena il gorgoglio degli eventi verso l'alto (e il componente dell'applicazione)

Ecco il codice

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

Il componente dell'applicazione:

D'altra parte, c'è il componente dell'applicazione (che è un genitore del componente a discesa):

  • Questo componente cattura ogni evento click ed emette sullo stesso oggetto rxjs ( userMenu )

Ecco il codice:

export class AppComponent {

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

Cosa mi dà fastidio:

  1. Non mi sento davvero a mio agio con l'idea di avere un soggetto globale che funga da connettore tra quei componenti.
  2. Il setTimeout : questo è necessario perché ecco cosa succede altrimenti se l'utente fa clic sul pulsante che mostra il menu a discesa:
    • L'utente fa clic sul pulsante (che non fa parte del componente a discesa) per mostrare il menu a discesa.
    • Viene visualizzato il menu a discesa e si iscrive immediatamente all'oggetto Menù utente .
    • L'evento click arriva al componente dell'app e viene catturato
    • Il componente dell'applicazione emette un evento sull'argomento userMenu
    • Il componente a discesa cattura questa azione sul menu utente e nasconde il menu a discesa.
    • Alla fine il menu a discesa non viene mai visualizzato.

Questo timeout impostato ritarda l'abbonamento alla fine dell'attuale turno del codice JavaScript che risolve il problema, ma secondo me in modo molto elegante.

Se conosci soluzioni più pulite, migliori, più intelligenti, più veloci o più forti, faccelo sapere :)!


Risposte:


245

È possibile utilizzare l' (document:click)evento:

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

Un altro approccio è quello di creare un evento personalizzato come direttiva. Dai un'occhiata a questi post di Ben Nadel:


1
@Sasxa grazie, e d'accordo. Ho pensato che se ci fosse un documento API non deprecato, sarebbe apparso nella ricerca che mi ha portato qui.
danludwig,

4
Se event.target è un elemento che è stato aggiunto dinamicamente attraverso qualcosa come un binding [innerHTML], il nativeElement di elementRef non lo contenga.
Patrick Graham,

8
L'unico aspetto negativo di questa tecnica è che ora hai un listener di eventi click nella tua applicazione che si attiva ogni volta che fai clic.
codeepic,

37
Secondo la guida ufficiale di stile Angular 2, è necessario utilizzare al @HostListener('document:click', ['$event'])posto della hostproprietà sul Componentdecoratore.
Michał Miszczyszyn,

15
o potresti semplicemente usare rxjs per questo, come Observable.fromEvent(document, 'click').subscribe(event => {your code here}), quindi puoi sempre iscriverti solo quando devi ascoltare, ad esempio, hai aperto il menu a discesa e quando lo chiudi annulli l'iscrizione
Blind Despair

43

METODO ELEGANTE

Ho trovato questa clickOutdirettiva: https://github.com/chliebel/angular2-click-outside . Lo controllo e funziona bene (copio solo clickOutside.directive.tsnel mio progetto). Puoi usarlo in questo modo:

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

Dov'è la closetua funzione che verrà chiamata quando l'utente fa clic al di fuori di div. È un modo molto elegante per gestire il problema descritto nella domanda.

Se usi la direttiva sopra per chiudere la finestra popUp, ricorda innanzitutto di aggiungere event.stopPropagation()al gestore eventi click-button che apre popUp.

BONUS:

Di seguito copio il codice della direttiva originale dal file clickOutside.directive.ts(nel caso in cui il link smetterà di funzionare in futuro) - l'autore è Christian Liebel :


2
@Vega La mia raccomandazione sarebbe di usare la Direttiva in un elemento con * ngSe, nel caso di dropdown, questo può essere qualcosa del genere<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Gabriel Balsa Cantú,

19

L'ho fatto in questo modo.

Aggiunto un listener di eventi sul documento clicke in quel gestore controllato se il mio containercontiene event.target, in caso contrario - nascondere il menu a discesa.

Sembrerebbe così.

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

Ciao. The.bind è (questo) necessario?
Drenai,

1
@Brian Potrebbe essere o non essere necessario, ma sicuramente non lo sarebbe se racchiudesse la this.offClickHandlerfunzione freccia in.
Lansana Camara,

17

Penso che la risposta accettata da Sasxa funzioni per la maggior parte delle persone. Tuttavia, ho avuto una situazione in cui il contenuto dell'Elemento, che dovrebbe ascoltare gli eventi off-click, è cambiato dinamicamente. Quindi Elements nativeElement non conteneva event.target, quando è stato creato dinamicamente. Potrei risolverlo con la seguente direttiva

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

Invece di verificare se elementRef contiene event.target, controllo se elementRef si trova nel percorso (percorso DOM alla destinazione) dell'evento. In questo modo è possibile gestire elementi creati dinamicamente.


Grazie - funziona meglio quando sono presenti componenti figlio
MAhsan

Questo mi è stato molto utile. non sono sicuro del motivo per cui al di fuori del clic del componente non è stata rilevata alcuna risposta
JavaQuest,

13

Se lo stai facendo su iOS, usa anche l' touchstartevento:

A partire da Angular 4, HostListenerdecorare è il modo preferito per farlo

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

Oggi abbiamo lavorato su un problema simile al lavoro, cercando di capire come far scomparire un div a discesa quando viene cliccato. La nostra è leggermente diversa dalla domanda del poster iniziale perché non volevamo fare clic su un componente o una direttiva diversi , ma semplicemente al di fuori del div specifico.

Abbiamo finito per risolverlo usando il gestore di eventi (window: mouseup).

Passaggi:
1.) Abbiamo assegnato all'intero menu a discesa div un nome di classe univoco.

2.) Nel menu a discesa interno stesso (l'unica parte su cui volevamo fare clic per NON chiudere il menu), abbiamo aggiunto un gestore di eventi (finestra: mouseup) e passato l'evento $.

NOTA: non è stato possibile eseguirlo con un tipico gestore di "clic" poiché questo era in conflitto con il gestore di clic principale.

3.) Nel nostro controller, abbiamo creato il metodo che volevamo essere chiamato nell'evento click-out e usiamo event.closest ( documenti qui ) per scoprire se lo spot cliccato è all'interno del nostro div di classe target.

 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" dovrebbe essere usato all'interno del decoratore host.
Shivam,

@ Shivam-- Non sono sicuro di cosa intendi per "dovrebbe essere usato all'interno del decoratore host." Potresti forse spiegare ulteriormente? Grazie!
Paige Bolduc,

Intendo invece di usare direttamente l'oggetto "finestra", dovresti usare la proprietà "host" del decoratore del componente / decoratore del componente "HostListener". Questa è una pratica standard mentre si lavora con l'oggetto "finestra" o "documento" in angolare 2.
Shivam

2
Tieni d'occhio la compatibilità del browser, .closest()non è supportato su IE / Edge da oggi ( caniuse )
superjos

5

È possibile creare un elemento di pari livello nel menu a discesa che copre l'intero schermo che sarebbe invisibile ed essere lì solo per acquisire eventi di clic. Quindi è possibile rilevare i clic su quell'elemento e chiudere il menu a discesa quando si fa clic. Diciamo che l'elemento è di serigrafia di classe, ecco un po 'di stile:

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

L'indice z deve essere abbastanza alto da posizionarlo al di sopra di tutto tranne che del menu a discesa. In questo caso il mio menu a discesa verrebbe b-indice 2.

Le altre risposte hanno funzionato in alcuni casi per me, tranne che a volte il mio menu a discesa si chiudeva quando interagivo con elementi al suo interno e non lo volevo. Avevo aggiunto dinamicamente elementi che non erano contenuti nel mio componente, in base al target dell'evento, come mi aspettavo. Piuttosto che sistemare quel casino, ho pensato di provarlo nel modo serigrafico.


5

Non ho fatto alcuna soluzione alternativa. Ho appena allegato un documento: fai clic sulla mia funzione di attivazione / disattivazione come segue:

    @Direttiva({
      selettore: '[appDropDown]'
    })
    classe di esportazione DropdownDirective implementa OnInit {

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

      costruttore (elemRef privato: ElementRef) {}

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

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

Quindi, quando sono fuori dalla mia direttiva, chiudo il menu a discesa.


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

Lati negativi: - Listener di eventi a due clic per ciascuno di questi componenti sulla pagina. Non utilizzarlo su componenti presenti nella pagina centinaia di volte.


No, l'ho usato solo sul browser desktop.
Alex Egli,

3

Vorrei integrare la risposta @Tony, poiché l'evento non viene rimosso dopo il clic all'esterno del componente. Ricevuta completa:

  • Segna il tuo elemento principale con #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);
  • Sull'elemento cliccabile, utilizzare:

    (click)="dropstatus=true"

Ora puoi controllare il tuo stato a discesa con la variabile dropstatus e applicare le classi appropriate con [ngClass] ...


3

Puoi scrivere la direttiva:

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


}

Nel tuo componente:

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

Questa direttiva emette un evento quando l'elemento html contiene in DOM e quando la proprietà di input [clickout] è "true". Ascolta l'evento mousedown per gestire l'evento prima che l'elemento venga rimosso dal DOM.

E una nota: firefox non contiene la proprietà 'path' sull'evento che puoi usare per creare il percorso:

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

Quindi dovresti cambiare il gestore eventi sulla direttiva: event.path dovrebbe essere sostituito getEventPath (event)

Questo modulo può essere d'aiuto. https://www.npmjs.com/package/ngx-clickout Contiene la stessa logica ma gestisce anche l'evento esc sull'elemento html di origine.


3

La risposta corretta ha un problema, se hai un componente clicakble nel tuo popover, l'elemento non sarà più sul containmetodo e si chiuderà, in base a @ JuHarm89 ho creato il mio:

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

Grazie per l'aiuto!


2

Una versione migliore per @Tony ottima soluzione:

@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 un file css: // NON necessario se si utilizza il menu a discesa bootstrap.

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

2

Dovresti invece verificare se fai clic sull'overlay modale, molto più semplice.

Il tuo modello:

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

E il metodo:

  @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

Ho fatto una direttiva per affrontare questo problema simile e sto usando Bootstrap. Ma nel mio caso, invece di aspettare che l'evento click all'esterno dell'elemento chiuda l'attuale menu a discesa aperto, penso che sia meglio se guardiamo l'evento "mouseleave" per chiudere automaticamente il menu.

Ecco la mia soluzione:

Direttiva

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

Se si utilizza Bootstrap, è possibile farlo direttamente con il modo bootstrap tramite i menu a discesa (componente 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>

Ora va bene mettere (click)="clickButton()"roba sul pulsante. http://getbootstrap.com/javascript/#dropdowns


1

Ho anche fatto un po 'di soluzione per conto mio.

Ho creato un evento (dropdownOpen) che ascolto nel mio componente ng-select element e chiamo una funzione che chiuderà tutti gli altri SelectComponent aperti a parte il SelectComponent attualmente aperto.

Ho modificato una funzione all'interno del file select.ts come sotto per emettere l'evento:

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

Nel codice HTML ho aggiunto un listener di eventi per (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>

Questa è la mia funzione di chiamata sul trigger di evento all'interno del componente con 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();
    })
}

1

NOTA: per coloro che desiderano utilizzare i Web worker ed è necessario evitare l'uso di documenti e nativeElement, questo funzionerà.

Ho risposto alla stessa domanda qui: /programming/47571144

Copia / Incolla dal link sopra:

Ho avuto lo stesso problema quando stavo creando un menu a discesa e una finestra di dialogo di conferma che volevo eliminare quando facevo clic all'esterno.

La mia implementazione finale funziona perfettamente ma richiede alcune animazioni e stili css3.

NOTA : non ho testato il codice seguente, potrebbero esserci dei problemi di sintassi che devono essere risolti, anche le ovvie modifiche per il tuo progetto!

Cosa ho fatto:

Ho creato un div fisso separato con altezza 100%, larghezza 100% e trasformazione: scala (0), questo è essenzialmente lo sfondo, puoi modellarlo con il colore di sfondo: rgba (0, 0, 0, 0.466); per rendere evidente il menu è aperto e lo sfondo è click-to-close. Il menu ottiene un indice z più alto di tutto il resto, quindi il div in background ottiene un indice z più basso del menu ma anche più alto di tutto il resto. Quindi lo sfondo ha un evento click che chiude il menu a discesa.

Eccolo qui con il tuo codice 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>

Ecco il css3 che necessita di alcune semplici animazioni.

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

Se non cerchi qualcosa di visivo, puoi semplicemente usare una transizione come questa

.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

Mi sono imbattuto in un'altra soluzione, ispirata da esempi con l'evento focus / blur.

Pertanto, se si desidera ottenere la stessa funzionalità senza associare un listener di documenti globale, è possibile considerare valido il seguente esempio. Funziona anche su Safari e Firefox su OSx, nonostante abbiano un'altra gestione dell'evento focus pulsante: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Esempio di lavoro su stackbiz con angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts

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

La direttiva sarà simile a questa:

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

È possibile utilizzare mouseleavenella tua vista in questo modo

Prova con 8 angolari e funziona perfettamente

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

Questo chiuderà il contenitore quando il topo se ne va, ma grazie per averlo condiviso, poiché non ero a conoscenza della sua esistenza.
Ben Hayward,

0

IL METODO PIÙ ELEGANTE: D

C'è un modo più semplice per farlo, senza bisogno di direttive per questo.

"element-that-toggle-your-dropdown" dovrebbe essere un tag pulsante. Utilizzare qualsiasi metodo nell'attributo (sfocatura). È tutto.

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

Questo non funzionerà se si desidera mantenere aperto il menu a discesa al clic, ad esempio un utente potrebbe perdere un clic su un pulsante
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.