Làm thế nào tôi có thể đóng một thả xuống khi nhấp vào bên ngoài?


144

Tôi muốn đóng trình đơn thả xuống đăng nhập của mình khi người dùng nhấp vào bất cứ nơi nào bên ngoài danh sách thả xuống đó và tôi muốn làm điều đó với Angular2 và với "cách tiếp cận" Angular2 ...

Tôi đã thực hiện một giải pháp, nhưng tôi thực sự không cảm thấy tự tin với nó. Tôi nghĩ rằng phải có một cách dễ nhất để đạt được kết quả tương tự, vì vậy nếu bạn có bất kỳ ý tưởng nào ... hãy thảo luận :)!

Đây là cách thực hiện của tôi:

Thành phần thả xuống:

Đây là thành phần cho danh sách thả xuống của tôi:

  • Mỗi khi thành phần này được đặt thành hiển thị, (Ví dụ: khi người dùng nhấp vào nút để hiển thị), nó sẽ đăng ký một chủ đề rxjs "toàn cầu" userMothy được lưu trữ trong Đối tượng dịch vụ .
  • Và mỗi khi nó bị ẩn, nó sẽ hủy đăng ký chủ đề này.
  • Mỗi lần nhấp vào bất cứ nơi nào trong mẫu của thành phần này sẽ kích hoạt phương thức onClick () , chỉ dừng sự kiện sủi bọt lên trên cùng (và thành phần ứng dụng)

Đây là mã

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

Thành phần ứng dụng:

Mặt khác, có thành phần ứng dụng (là cha mẹ của thành phần thả xuống):

  • Thành phần này bắt mọi sự kiện nhấp chuột và phát ra trên cùng Chủ đề rxjs ( userMothy )

Đây là mã:

export class AppComponent {

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

Điều gì làm phiền tôi:

  1. Tôi không cảm thấy thực sự thoải mái với ý tưởng có một Chủ thể toàn cầu đóng vai trò là người kết nối giữa các thành phần đó.
  2. Các setTimeout : này là cần thiết vì đây là những gì xảy ra cách khác nếu nhấp chuột người dùng trên nút đó hiển thị danh sách thả xuống:
    • Người dùng nhấp vào nút (không phải là một phần của thành phần thả xuống) để hiển thị danh sách thả xuống.
    • Danh sách thả xuống được hiển thị và nó ngay lập tức đăng ký vào chủ đề userMothy .
    • Bong bóng sự kiện nhấp vào thành phần ứng dụng và bị bắt
    • Thành phần ứng dụng phát ra một sự kiện về chủ đề userMothy
    • Thành phần thả xuống bắt hành động này trên userMothy và ẩn danh sách thả xuống.
    • Vào cuối, thả xuống không bao giờ được hiển thị.

Việc thiết lập thời gian chờ này làm trì hoãn việc đăng ký đến hết lượt mã JavaScript hiện tại để giải quyết vấn đề, nhưng theo ý kiến ​​rất lịch sự theo quan điểm của tôi.

Nếu bạn biết các giải pháp sạch hơn, tốt hơn, thông minh hơn, nhanh hơn hoặc mạnh hơn, xin vui lòng cho tôi biết :)!


Những câu trả lời này có thể cung cấp cho bạn một số ý tưởng: stackoverflow.com/a/35028820/215945 , stackoverflow.com/questions/35024495#35024651
Mark Rajcok

Câu trả lời:


245

Bạn có thể sử dụng (document:click)sự kiện:

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

Một cách tiếp cận khác là tạo sự kiện tùy chỉnh như một chỉ thị. Kiểm tra các bài viết của Ben Nadel:


1
@Sasxa cảm ơn bạn, và đồng ý. Tôi đã hình dung nếu có một tài liệu API không bị phản đối, nó sẽ xuất hiện trong tìm kiếm dẫn tôi đến đây.
danludwig

4
Nếu event.target là một phần tử được thêm động thông qua một cái gì đó như ràng buộc [InternalHTML] thì phần tử gốc của phần tử sẽ không chứa phần tử đó.
Patrick Graham

8
Nhược điểm duy nhất của kỹ thuật này là bây giờ bạn có một trình lắng nghe sự kiện nhấp trong ứng dụng của bạn sẽ kích hoạt mỗi khi bạn nhấp.
codeepic

37
Theo hướng dẫn phong cách chính thức của Angular 2, bạn nên sử dụng @HostListener('document:click', ['$event'])thay vì hosttài sản trên Componenttrang trí.
Michał Miszczyszyn

15
hoặc bạn chỉ có thể sử dụng rxjs cho nó, như Observable.fromEvent(document, 'click').subscribe(event => {your code here})vậy, vì vậy bạn luôn có thể đăng ký chỉ khi bạn cần nghe ví dụ như bạn đã mở thả xuống và khi bạn đóng nó, bạn hủy đăng ký
Tuyệt vọng mù

42

PHƯƠNG PHÁP TUYỆT VỜI

Tôi tìm thấy clickOutchỉ thị này : https://github.com/chliebel/angular2-click-outside . Tôi kiểm tra nó và nó hoạt động tốt (tôi chỉ sao chép clickOutside.directive.tsvào dự án của tôi). Bạn có thể sử dụng nó theo cách này:

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

Trong trường hợp closelà chức năng của bạn sẽ được gọi khi người dùng nhấp chuột bên ngoài div. Đó là cách rất thanh lịch để xử lý vấn đề được mô tả trong câu hỏi.

Nếu bạn sử dụng chỉ thị trên để đóng cửa sổ popUp, trước tiên hãy nhớ thêm event.stopPropagation()vào nút bấm xử lý sự kiện mở popUp.

TẶNG KEM:

Dưới đây tôi sao chép mã chỉ thị oryginal từ tệp clickOutside.directive.ts(trong trường hợp nếu liên kết sẽ ngừng hoạt động trong tương lai) - tác giả là Christian Liebel :


2
@Vega Giới thiệu của tôi sẽ là sử dụng Chỉ thị trong một yếu tố với * ngNếu trong trường hợp thả xuống, đây có thể là một cái gì đó giống như<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Gabriel Balsa Cantú

19

Tôi đã làm theo cách này.

Đã thêm một trình lắng nghe sự kiện trên tài liệu clickvà trong trình xử lý đó đã kiểm tra xem phần containerchứa của tôi event.target, nếu không - ẩn phần thả xuống.

Nó sẽ trông như thế này.

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

Chào. Là.bind (điều này) cần thiết?
Drenai

1
@Brian Có thể hoặc không cần thiết, nhưng chắc chắn sẽ không nếu anh ta bọc this.offClickHandlerchức năng mũi tên.
Lansana Camara

17

Tôi nghĩ rằng câu trả lời được chấp nhận của Sasxa cho hầu hết mọi người. Tuy nhiên, tôi đã có một tình huống, trong đó nội dung của Element, sẽ lắng nghe các sự kiện nhấp chuột, thay đổi linh hoạt. Vì vậy, các yếu tố bản địa Element không chứa event.target, khi nó được tạo động. Tôi có thể giải quyết điều này bằng chỉ thị sau

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

Thay vì kiểm tra xem ElementRef có chứa event.target hay không, tôi kiểm tra xem ElementRef có trong đường dẫn (đường dẫn DOM tới đích) của sự kiện không. Bằng cách đó, có thể xử lý các yếu tố được tạo động.


Cảm ơn - điều này hoạt động tốt hơn khi có các thành phần trẻ em
MAhsan 16/03/18

Điều này rất hữu ích cho tôi. không chắc chắn tại sao bên ngoài nhấp chuột thành phần không được phát hiện với các câu trả lời khác.
JavaQuest

13

Nếu bạn đang làm điều này trên iOS, hãy sử dụng touchstartsự kiện này:

Kể từ Angular 4, HostListenertrang trí là cách ưa thích để làm điều này

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

Chúng tôi đã làm việc về một vấn đề tương tự tại nơi làm việc ngày hôm nay, cố gắng tìm ra cách làm cho một div thả xuống biến mất khi nó được nhấp vào. Câu hỏi của chúng tôi hơi khác so với câu hỏi của người đăng ban đầu vì chúng tôi không muốn nhấp vào một thành phần hoặc chỉ thị khác , mà chỉ ở bên ngoài div cụ thể.

Chúng tôi đã kết thúc việc giải quyết nó bằng cách sử dụng trình xử lý sự kiện (window: mouseup).

Các bước:
1.) Chúng tôi đã cho toàn bộ menu thả xuống div một tên lớp duy nhất.

2.) Trên chính menu thả xuống bên trong (phần duy nhất mà chúng tôi muốn nhấp để KHÔNG đóng menu), chúng tôi đã thêm một trình xử lý sự kiện (window: mouseup) và chuyển vào sự kiện $.

LƯU Ý: Không thể thực hiện được với trình xử lý "nhấp chuột" thông thường vì điều này mâu thuẫn với trình xử lý nhấp chuột gốc.

3.) Trong bộ điều khiển của chúng tôi, chúng tôi đã tạo ra phương thức mà chúng tôi muốn được gọi trong sự kiện nhấp ra và chúng tôi sử dụng event.closest ( tài liệu ở đây ) để tìm hiểu xem vị trí được nhấp có nằm trong div lớp mục tiêu của chúng tôi không.

 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" nên được sử dụng trong trình trang trí máy chủ.
Shivam

@ Shivam-- Tôi không chắc ý của bạn là "nên được sử dụng trong trang trí máy chủ." Bạn có thể giải thích thêm? Cảm ơn!
Paige Bolduc

Ý tôi là thay vì sử dụng trực tiếp đối tượng "window", bạn nên sử dụng thuộc tính "host" của trình trang trí thành phần / "HostListener" của thành phần. Đó là thực hành tiêu chuẩn trong khi làm việc với đối tượng "cửa sổ" hoặc "tài liệu" ở góc 2.
Shivam

2
Chỉ cần theo dõi khả năng tương thích trình duyệt, .closest()không được hỗ trợ trên IE / Edge cho đến ngày hôm nay ( caniuse )
superjos

5

Bạn có thể tạo một yếu tố anh chị em với danh sách thả xuống bao phủ toàn bộ màn hình sẽ vô hình và ở đó chỉ để chụp các sự kiện nhấp chuột. Sau đó, bạn có thể phát hiện các nhấp chuột vào phần tử đó và đóng danh sách thả xuống khi nó được nhấp. Hãy nói rằng phần tử đó là của lớp lụa, đây là một số kiểu cho nó:

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

Chỉ số z cần phải đủ cao để đặt nó lên trên tất cả mọi thứ trừ danh sách thả xuống của bạn. Trong trường hợp này, danh sách thả xuống của tôi sẽ b z-index 2.

Các câu trả lời khác hoạt động trong một số trường hợp đối với tôi, ngoại trừ đôi khi danh sách thả xuống của tôi đóng lại khi tôi tương tác với các yếu tố trong đó và tôi không muốn điều đó. Tôi đã tự động thêm các yếu tố không có trong thành phần của mình, theo mục tiêu sự kiện, như tôi mong đợi. Thay vì sắp xếp mớ hỗn độn đó, tôi nghĩ rằng tôi chỉ nên thử nó theo cách màn hình.


5

Tôi đã không làm cho bất kỳ giải pháp. Tôi vừa đính kèm tài liệu: nhấp vào chức năng chuyển đổi của tôi như sau:

    @ Định hướng ({
      bộ chọn: '[appDropDown]'
    })
    lớp xuất khẩu DropdownDirective thực hiện OnInit {

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

      hàm tạo (riêng elemRef: ElementRef) {}

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

      @HostListener ('tài liệu: nhấp', ['$ event'])
      @HostListener ('tài liệu: touchstart', ['$ event'])
      chuyển đổi (sự kiện) {
        if (this.elemRef.nativeEuity.contains (event.target)) {
          this .isOpen =! this.isOpen;
        } khác {
          this .isOpen = false;
      }
    }

Vì vậy, khi tôi ở ngoài chỉ thị của mình, tôi đóng cửa thả xuống.


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

TẢI XUỐNG: - Trình nghe sự kiện hai lần nhấp cho mỗi một trong các thành phần này trên trang. Không sử dụng điều này trên các thành phần trên trang hàng trăm lần.


Không, tôi chỉ sử dụng nó trên trình duyệt máy tính để bàn.
Alex Egli

3

Tôi muốn bổ sung câu trả lời @Tony, vì sự kiện này không bị xóa sau khi nhấp vào bên ngoài thành phần. Hoàn thành biên lai:

  • Đánh dấu phần tử chính của bạn bằng #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);
  • Trên phần tử có thể nhấp, sử dụng:

    (click)="dropstatus=true"

Bây giờ bạn có thể kiểm soát trạng thái thả xuống của mình bằng biến dropstatus và áp dụng các lớp thích hợp với [ngClass] ...


3

Bạn có thể viết chỉ thị:

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


}

Trong thành phần của bạn:

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

Lệnh này phát ra sự kiện khi phần tử html có trong DOM và khi thuộc tính đầu vào [clickOut] là 'true'. Nó lắng nghe sự kiện mousedown để xử lý sự kiện trước khi phần tử sẽ bị xóa khỏi DOM.

Và một lưu ý: firefox không chứa 'đường dẫn' thuộc tính trong trường hợp bạn có thể sử dụng hàm để tạo đường dẫn:

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

Vì vậy, bạn nên thay đổi trình xử lý sự kiện theo chỉ thị: event.path nên được thay thế getEventPath (sự kiện)

Mô-đun này có thể giúp đỡ. https://www.npmjs.com/package/ngx-clickout Nó chứa logic tương tự nhưng cũng xử lý sự kiện esc trên phần tử html nguồn.


3

Câu trả lời đúng có một vấn đề, nếu bạn có thành phần clicakble trong cửa sổ bật lên của mình, phần tử sẽ không còn trên containphương thức nữa và sẽ đóng lại, dựa trên @ JuHarm89 tôi tự tạo:

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

Cảm ơn đã giúp đỡ!


2

Phiên bản tốt hơn cho giải pháp tuyệt vời @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");

        }
    }
}

Trong tệp css: // KHÔNG cần nếu bạn sử dụng trình đơn thả xuống bootstrap.

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

2

Bạn nên kiểm tra nếu bạn nhấp vào lớp phủ thay thế, dễ dàng hơn rất nhiều.

Mẫu của bạn:

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

Và phương pháp:

  @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

Tôi đã đưa ra một chỉ thị để giải quyết vấn đề tương tự này và tôi đang sử dụng Bootstrap. Nhưng trong trường hợp của tôi, thay vì chờ đợi sự kiện nhấp bên ngoài phần tử để đóng menu thả xuống đã mở hiện tại, tôi nghĩ sẽ tốt hơn nếu chúng ta theo dõi sự kiện 'mouseleave' để tự động đóng menu.

Đây là giải pháp của tôi:

Chỉ thị

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

Nếu bạn đang sử dụng Bootstrap, bạn có thể thực hiện trực tiếp với cách bootstrap thông qua danh sách thả xuống (thành phần 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>

Bây giờ không sao để đặt (click)="clickButton()"công cụ vào nút. http://getbootstrap.com/javascript/#dropdowns


1

Tôi cũng đã làm một chút cách giải quyết của riêng tôi.

Tôi đã tạo một sự kiện (dropdownOpen) mà tôi nghe tại thành phần phần tử ng-select của mình và gọi một hàm sẽ đóng tất cả các SelectComponent khác được mở ra ngoài phần ChọnComponent hiện đang mở.

Tôi đã sửa đổi một hàm bên trong tệp select.ts như bên dưới để phát ra sự kiện:

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

Trong HTML tôi đã thêm một trình lắng nghe sự kiện cho (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>

Đây là chức năng gọi của tôi trên trình kích hoạt sự kiện bên trong thành phần có thẻ 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

LƯU Ý: Đối với những người muốn sử dụng nhân viên web và bạn cần tránh sử dụng tài liệu và nguồn gốc, điều này sẽ hoạt động.

Tôi đã trả lời câu hỏi tương tự ở đây: /programming/47571144

Sao chép / Dán từ liên kết trên:

Tôi gặp vấn đề tương tự khi tôi làm menu thả xuống và hộp thoại xác nhận tôi muốn loại bỏ chúng khi nhấp vào bên ngoài.

Việc thực hiện cuối cùng của tôi hoạt động hoàn hảo nhưng đòi hỏi một số hình ảnh động và kiểu dáng css3.

LƯU Ý : Tôi chưa kiểm tra mã dưới đây, có thể có một số vấn đề cú pháp cần được giải quyết, cũng là điều chỉnh rõ ràng cho dự án của riêng bạn!

Tôi đã làm gì:

Tôi đã tạo một div cố định riêng biệt với chiều cao 100%, chiều rộng 100% và biến đổi: scale (0), đây thực chất là nền, bạn có thể tạo kiểu cho nó với màu nền: rgba (0, 0, 0, 0.466); để làm cho rõ ràng menu đang mở và nền là nhấp để đóng. Menu có chỉ số z cao hơn mọi thứ khác, sau đó div nền có chỉ số z thấp hơn menu nhưng cũng cao hơn mọi thứ khác. Sau đó, nền có một sự kiện nhấp để đóng thả xuống.

Đây là mã html của bạn.

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

Đây là css3 cần một số hình ảnh động đơn giản.

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

Nếu bạn không theo bất cứ điều gì trực quan, bạn chỉ có thể sử dụng một chuyển đổi như thế này

.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

Tôi đã đi đến một giải pháp khác, lấy cảm hứng từ các ví dụ với sự kiện tập trung / mờ.

Vì vậy, nếu bạn muốn đạt được chức năng tương tự mà không cần đính kèm trình nghe tài liệu toàn cầu, bạn có thể xem xét như một ví dụ hợp lệ sau đây. Nó cũng hoạt động trong Safari và Firefox trên OSx, mặc dù họ có cách xử lý sự kiện tập trung nút khác: https://developer.mozilla.org/en-US/docs/Web/HTML/Euity/button#Clicking_and_f Focus

Ví dụ hoạt động trên stackbiz với góc 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts

Đánh dấu 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>

Lệnh sẽ như thế này:

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

Bạn có thể sử dụng mouseleavetrong quan điểm của bạn như thế này

Kiểm tra với góc 8 và hoạt động hoàn hảo

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

Điều này sẽ đóng container khi chuột rời khỏi, nhưng dù sao cũng cảm ơn bạn đã chia sẻ vì tôi không biết về sự tồn tại của nó.
Ben Hayward

0

PHƯƠNG PHÁP TUYỆT VỜI NHẤT: D

Có một cách dễ nhất để làm điều đó, không cần bất kỳ chỉ thị nào cho việc đó.

"Element-that-toggle-your-dropdown" phải là thẻ nút. Sử dụng bất kỳ phương thức nào trong thuộc tính (làm mờ). Đó là tất cả.

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

Điều này sẽ không hoạt động nếu bạn muốn giữ danh sách thả xuống khi nhấp, ví dụ người dùng có thể bỏ lỡ nhấp vào nút
Có khi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.