Bagaimana saya bisa menutup dropdown klik di luar?

144

Saya ingin menutup dropdown menu login saya ketika pengguna mengklik di mana saja di luar dropdown itu, dan saya ingin melakukannya dengan Angular2 dan dengan "pendekatan" Angular2 ...

Saya telah mengimplementasikan solusi, tetapi saya benar-benar tidak merasa yakin dengan itu. Saya pikir pasti ada cara termudah untuk mencapai hasil yang sama, jadi jika Anda punya ide ... mari kita bahas :)!

Inilah implementasi saya:

Komponen dropdown:

Ini adalah komponen untuk dropdown saya:

  • Setiap kali komponen ini disetel ke terlihat, (Misalnya: ketika pengguna mengklik tombol untuk menampilkannya) ia berlangganan ke subjek "global" rxjs yang disimpan oleh userMenu di dalam SubjectsService .
  • Dan setiap kali disembunyikan, ia berhenti berlangganan ke subjek ini.
  • Setiap klik di mana saja di dalam templat komponen ini memicu metode onClick () , yang hanya menghentikan peristiwa yang menggelegak ke atas (dan komponen aplikasi)

Ini kodenya

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

Komponen aplikasi:

Di sisi lain, ada komponen aplikasi (yang merupakan induk dari komponen dropdown):

  • Komponen ini menangkap setiap acara klik dan memancarkan pada subjek rxjs yang sama ( userMenu )

Ini kodenya:

export class AppComponent {

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

Apa yang mengganggu saya:

  1. Saya tidak merasa benar-benar nyaman dengan gagasan memiliki Subjek global yang bertindak sebagai penghubung antara komponen-komponen itu.
  2. The setTimeout : Hal ini diperlukan karena di sini adalah apa yang terjadi sebaliknya jika pengguna mengklik tombol yang menunjukkan dropdown:
    • Pengguna mengklik tombol (yang bukan merupakan bagian dari komponen dropdown) untuk menampilkan dropdown.
    • Dropdown ditampilkan dan segera berlangganan ke subjek userMenu .
    • Acara klik menggembung ke komponen aplikasi dan tertangkap
    • Komponen aplikasi memancarkan suatu peristiwa pada subjek userMenu
    • Komponen dropdown menangkap tindakan ini pada userMenu dan menyembunyikan dropdown.
    • Pada akhirnya dropdown tidak pernah ditampilkan.

Set timeout ini menunda langganan ke akhir pergantian kode JavaScript saat ini yang menyelesaikan masalah, tetapi menurut cara yang sangat elegan menurut saya.

Jika Anda tahu solusi yang lebih bersih, lebih baik, lebih cerdas, lebih cepat atau lebih kuat, beri tahu saya :)!

Sejuk
sumber
Jawaban ini dapat memberi Anda beberapa ide: stackoverflow.com/a/35028820/215945 , stackoverflow.com/questions/35024495#35024651
Mark Rajcok

Jawaban:

245

Anda dapat menggunakan (document:click)acara:

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

Pendekatan lain adalah membuat acara khusus sebagai arahan. Lihatlah pos-pos ini oleh Ben Nadel:

Sasxa
sumber
1
@Sasxa terima kasih, dan setuju. Saya pikir jika ada dokumen API yang tidak usang, itu akan muncul dalam pencarian yang membawa saya ke sini.
danludwig
4
Jika event.target adalah elemen yang ditambahkan secara dinamis melalui sesuatu seperti ikatan [innerHTML] maka nativeElement elementRef tidak akan mengandungnya.
Patrick Graham
8
Satu-satunya downside ke teknik ini adalah bahwa sekarang Anda memiliki pendengar acara klik dalam aplikasi Anda yang menyala setiap kali Anda mengklik.
codeepic
37
Menurut pejabat sudut 2 panduan gaya, Anda harus menggunakan @HostListener('document:click', ['$event'])bukan hostproperti di Componentdekorator.
Michał Miszczyszyn
15
atau Anda bisa menggunakan rxjs untuk itu, seperti Observable.fromEvent(document, 'click').subscribe(event => {your code here}), jadi Anda selalu dapat berlangganan hanya ketika Anda perlu mendengarkan misalnya Anda membuka dropdown, dan ketika Anda menutupnya Anda berhenti berlangganan
Blind Despair
43

METODE ELEGAN

Saya menemukan clickOutarahan ini : https://github.com/chliebel/angular2-click-outside . Saya memeriksanya dan berfungsi dengan baik (saya hanya menyalin clickOutside.directive.tske proyek saya). Anda dapat menggunakannya dengan cara ini:

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

Di mana closefungsi Anda yang akan dipanggil ketika pengguna mengklik di luar div. Ini adalah cara yang sangat elegan untuk menangani masalah yang dijelaskan dalam pertanyaan.

Jika Anda menggunakan arahan di atas untuk menutup jendela popUp, ingatlah terlebih dahulu untuk menambahkan event.stopPropagation()tombol pengendali acara yang membuka popUp.

BONUS:

Di bawah ini saya menyalin kode arahan oryginal dari file clickOutside.directive.ts(jika tautan akan berhenti berfungsi di masa mendatang) - penulisnya adalah Christian Liebel :

Kamil Kiełczewski
sumber
2
@Vega Rekomendasi saya adalah menggunakan Directive dalam elemen dengan * ngIf, dalam kasus dropdown, ini bisa menjadi sesuatu seperti<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Gabriel Balsa Cantú
19

Saya sudah melakukannya dengan cara ini.

Menambahkan pendengar acara pada dokumen clickdan dalam handler itu memeriksa jika saya containerberisi event.target, jika tidak - sembunyikan dropdown.

Akan terlihat seperti ini.

@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";
        }
    }
}
Tony
sumber
Hai. Apakah the.bind (ini) diperlukan?
Drenai
1
@Brian Mungkin atau mungkin tidak perlu, tapi pasti tidak akan jika dia membungkus this.offClickHandlerfungsi panah.
Lansana Camara
17

Saya pikir Sasxa menerima jawaban berfungsi untuk kebanyakan orang. Namun, saya mengalami situasi, di mana konten Elemen, yang seharusnya mendengarkan acara di luar klik, berubah secara dinamis. Jadi Elemen nativeElement tidak mengandung event.target, saat itu dibuat secara dinamis. Saya bisa menyelesaikan ini dengan arahan berikut

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

Alih-alih memeriksa apakah elementRef berisi event.target, saya memeriksa apakah elementRef ada di jalur (jalur DOM ke target) dari acara tersebut. Dengan begitu dimungkinkan untuk menangani Elemen yang dibuat secara dinamis.

JuHarm89
sumber
Terima kasih - ini berfungsi lebih baik ketika komponen anak ada
MAhsan
Ini sangat membantu saya. tidak yakin mengapa di luar klik komponen tidak terdeteksi dengan jawaban lain.
JavaQuest
13

Jika Anda melakukan ini di iOS, gunakan touchstart acara ini juga:

Pada Angular 4, HostListenermenghias adalah cara yang disukai untuk melakukan ini

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

}
Xavier
sumber
10

Kami telah mengerjakan masalah serupa di tempat kerja hari ini, mencoba mencari cara bagaimana membuat dropdown div menghilang ketika diklik. Pertanyaan kami sedikit berbeda dari pertanyaan poster awal karena kami tidak ingin mengeklik komponen atau arahan yang berbeda , tetapi hanya di luar div tertentu.

Kami akhirnya menyelesaikannya dengan menggunakan pengendali event (window: mouseup).

Langkah-langkah:
1.) Kami memberikan seluruh div menu dropdown nama kelas yang unik.

2.) Pada menu dropdown dalam itu sendiri (satu-satunya bagian yang kita inginkan klik untuk TIDAK menutup menu), kami menambahkan event handler (window: mouseup) dan lulus dalam $ event.

CATATAN: Ini tidak dapat dilakukan dengan handler "klik" yang khas karena ini bertentangan dengan handler klik induk.

3.) Di controller kami, kami menciptakan metode yang kami ingin dipanggil pada acara klik, dan kami menggunakan event.closest ( docs di sini ) untuk mengetahui apakah tempat yang diklik berada di dalam kelas kelas yang ditargetkan.

 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>

Paige Bolduc
sumber
"window: mouseup" harus digunakan dalam dekorator host.
Shivam
@ Shivam - Saya tidak yakin apa yang Anda maksud dengan "harus digunakan dalam dekorator tuan rumah." Bisakah Anda menjelaskan lebih lanjut? Terima kasih!
Paige Bolduc
Maksud saya alih-alih menggunakan objek "jendela" secara langsung, Anda harus menggunakan properti "host" komponen dekorator / dekorator "HostListener" komponen. Itu adalah praktik standar saat bekerja dengan objek "jendela" atau "dokumen" dalam sudut 2.
Shivam
2
Perhatikan kompatibilitas browser, .closest() tidak didukung pada IE / Ujung untuk hari ini ( caniuse )
superjos
5

Anda bisa membuat elemen saudara pada dropdown yang menutupi seluruh layar yang tidak terlihat dan berada di sana hanya untuk menangkap peristiwa klik. Kemudian Anda bisa mendeteksi klik pada elemen itu dan menutup dropdown ketika diklik. Katakanlah elemen itu dari layar silks kelas, berikut beberapa gaya untuknya:

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

Indeks-z harus cukup tinggi untuk menempatkannya di atas segalanya kecuali dropdown Anda. Dalam hal ini dropdown saya akan b-indeks 2.

Jawaban lain bekerja dalam beberapa kasus untuk saya, kecuali kadang-kadang dropdown saya ditutup ketika saya berinteraksi dengan elemen-elemen di dalamnya dan saya tidak menginginkannya. Saya secara dinamis menambahkan elemen yang tidak terkandung dalam komponen saya, sesuai dengan target acara, seperti yang saya harapkan. Daripada memilah yang berantakan saya pikir saya hanya akan mencobanya dengan cara silkscreen.

Patrick Graham
sumber
5

Saya tidak menemukan solusi. Saya baru saja melampirkan dokumen: klik pada fungsi sakelar saya sebagai berikut:

    @Pengarahan({
      pemilih: '[appDropDown]'
    })
    kelas ekspor DropdownDirective mengimplementasikan OnInit {

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

      constructor (elemRef pribadi: ElementRef) {}

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

      @HostListener ('dokumen: klik', ['$ event'])
      @HostListener ('dokumen: touchstart', ['$ event'])
      beralih (acara) {
        if (this.elemRef.nativeElement.contains (event.target)) {
          this.isOpen =! this.isOpen;
        } lain {
          this.isOpen = false;
      }
    }

Jadi, ketika saya berada di luar arahan saya, saya menutup dropdown.

Elie Nehmé
sumber
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;
    }
}

BAWAHKAN: - Pendengar acara klik dua kali untuk setiap komponen ini di halaman. Jangan gunakan ini pada komponen yang ada di halaman ratusan kali.

Alex Egli
sumber
Tidak, saya hanya menggunakannya di browser desktop.
Alex Egli
3

Saya ingin melengkapi jawaban @Tony, karena acara tersebut tidak dihapus setelah klik di luar komponen. Tanda terima lengkap:

  • Tandai elemen utama Anda dengan #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);
  • Pada elemen yang dapat diklik, gunakan:

    (click)="dropstatus=true"

Sekarang Anda dapat mengontrol status dropdown Anda dengan variabel dropstatus, dan menerapkan kelas yang tepat dengan [ngClass] ...

Gauss
sumber
3

Anda dapat menulis arahan:

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


}

Di komponen Anda:

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

Acara memancarkan arahan ini ketika elemen html mengandung di DOM dan ketika properti input [clickOut] 'benar'. Ini mendengarkan acara mousedown untuk menangani acara sebelum elemen akan dihapus dari DOM.

Dan satu catatan: firefox tidak mengandung properti 'path' jika Anda dapat menggunakan fungsi untuk membuat 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;
};

Jadi, Anda harus mengubah event handler di directive: event.path harus diganti getEventPath (event)

Modul ini dapat membantu. https://www.npmjs.com/package/ngx-clickout Ini berisi logika yang sama tetapi juga menangani acara esc pada elemen html sumber.

Alex Mikitevich
sumber
3

Jawaban yang benar memiliki masalah, jika Anda memiliki komponen clicakble di popover Anda, elemen tidak akan lagi pada containmetode dan akan ditutup, berdasarkan @ JuHarm89 saya membuat sendiri:

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

Terima kasih untuk bantuannya!

Douglas Caina
sumber
2

Versi yang lebih baik untuk @Tony solusi hebat:

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

        }
    }
}

Dalam file css: // TIDAK diperlukan jika Anda menggunakan drop-down bootstrap.

.ourDropdown{
   display: none;
}
.ourDropdown.open{
   display: inherit;
}
Dudi
sumber
2

Anda harus memeriksa jika Anda mengklik pada overlay modal, jauh lebih mudah.

Template Anda:

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

Dan metodenya:

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


  }
Stefdelec
sumber
2

Saya telah membuat arahan untuk mengatasi masalah serupa ini dan saya menggunakan Bootstrap. Tetapi dalam kasus saya, daripada menunggu acara klik di luar elemen untuk menutup menu dropdown yang dibuka saat ini saya pikir lebih baik jika kita menonton acara 'mouseleave' untuk secara otomatis menutup menu.

Inilah solusi saya:

Pengarahan

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>
Lemuel Layola Apa
sumber
1

Jika Anda menggunakan Bootstrap, Anda dapat melakukannya langsung dengan cara bootstrap melalui dropdown (komponen 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>

Sekarang tidak apa-apa untuk meletakkan (click)="clickButton()"barang di tombol. http://getbootstrap.com/javascript/#dropdowns

Vusan
sumber
1

Saya juga melakukan sedikit penyelesaian sendiri.

Saya membuat acara (dropdownOpen) yang saya dengarkan di komponen elemen ng-select saya dan memanggil fungsi yang akan menutup semua SelectComponent lainnya yang dibuka terlepas dari SelectComponent yang saat ini dibuka.

Saya memodifikasi satu fungsi di dalam file select.ts seperti di bawah ini untuk memancarkan acara:

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

Dalam HTML saya menambahkan pendengar acara untuk (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>

Ini adalah fungsi panggilan saya pada pemicu acara di dalam komponen yang memiliki 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();
    })
}
Gaurav Pandvia
sumber
1

CATATAN: Bagi mereka yang ingin menggunakan pekerja web dan Anda harus menghindari menggunakan dokumen dan nativeElement, ini akan berfungsi.

Saya menjawab pertanyaan yang sama di sini: /programming/47571144

Salin / Tempel dari tautan di atas:

Saya memiliki masalah yang sama ketika saya membuat menu drop-down dan dialog konfirmasi yang ingin saya abaikan saat mengklik di luar.

Implementasi akhir saya berfungsi dengan baik tetapi membutuhkan beberapa animasi dan gaya css3.

CATATAN : saya belum menguji kode di bawah ini, mungkin ada beberapa masalah sintaks yang perlu disetrika, juga penyesuaian yang jelas untuk proyek Anda sendiri!

Apa yang saya lakukan:

Saya membuat div tetap terpisah dengan tinggi 100%, lebar 100% dan mengubah: skala (0), ini pada dasarnya adalah latar belakang, Anda dapat mengaturnya dengan warna-latar belakang: rgba (0, 0, 0, 0,466); untuk memperjelas menu terbuka dan latar belakang klik untuk menutup. Menu mendapat indeks-z lebih tinggi dari yang lainnya, maka div latar belakang mendapat indeks-z lebih rendah dari menu tetapi juga lebih tinggi dari yang lainnya. Kemudian latar belakang memiliki acara klik yang menutup drop-down.

Ini dia dengan kode html Anda.

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

Inilah css3 yang membutuhkan beberapa animasi sederhana.

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

Jika Anda tidak mengejar apa pun visual Anda hanya dapat menggunakan transisi seperti ini

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

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

Saya menemukan solusi lain, terinspirasi oleh contoh-contoh dengan acara fokus / blur.

Jadi, jika Anda ingin mencapai fungsi yang sama tanpa melampirkan pendengar dokumen global, Anda dapat mempertimbangkan sebagai contoh berikut yang valid. Ini juga berfungsi di Safari dan Firefox di OSx, meskipun mereka memiliki penanganan acara fokus tombol lainnya: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Contoh kerja pada stackbiz dengan sudut 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>

Arahan akan terlihat seperti ini:

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

Anda dapat menggunakannya mouseleavedalam tampilan seperti ini

Uji dengan sudut 8 dan bekerja dengan sempurna

<ul (mouseleave)="closeDropdown()"> </ul>
Tony Ngo
sumber
Ini akan menutup wadah ketika mouse pergi, tapi terima kasih sudah berbagi karena saya tidak mengetahui keberadaannya.
Ben Hayward
0

METODE PALING ELEGAN: D

Ada satu cara termudah untuk melakukan itu, tidak perlu arahan untuk itu.

"element-that-toggle-your-dropdown" harus berupa tag tombol. Gunakan metode apa pun dalam atribut (blur). Itu saja.

<button class="element-that-toggle-your-dropdown"
               (blur)="isDropdownOpen = false"
               (click)="isDropdownOpen = !isDropdownOpen">
</button>
George Reznichenko
sumber
Ini tidak akan berfungsi jika Anda ingin agar dropdown tetap terbuka di klik, misalnya pengguna mungkin kehilangan klik tombol
Yeswhen