Как сфокусировать угловой клик-аут
Это div
будет динамически отображаться на странице как восточная панель, когда open panel
кнопка нажата. Бул showEastPanel
Переменная - это то, что используется для открытия и закрытия восточной панели. Я пытаюсь использовать (clickoutside)
закрыть панель (настройка showEastPanel
значение false) однако открытая панель запускается сначала на угловом крючке, и для панели устанавливается значение true, затем false, и панель не отображается. Есть ли какой-нибудь способ "clickoutside", чтобы не включать кнопку?
<div [ngClass]="{'d-none': !showEastPanel, 'east-panel-container': showEastPanel}" (clickOutside)="ClosePanel()">
<div id="east-panel">
<ng-template #eastPanel></ng-template>
</div>
<button (click)="ShowPanel()">Open Panel</button>
11 ответов
Я бы сделал это, используя рекомендованный Angular подход, который также прост в разработке приложений в средах без доступа DOM, я имею в виду Renderer 2
класс, который представляет собой абстракцию, предоставляемую Angular в форме службы, которая позволяет манипулировать элементами вашего приложения без непосредственного прикосновения к DOM.
При таком подходе нужно вводить Renderer2
в ваш конструктор компонента, который Renderer2
позволяет нам listen
чтобы вызвать события элегантно. Это просто берет элемент, который вы собираетесь слушать, в качестве первого аргумента, который может быть window
, document
, body
или любой другой элемент ссылки. Для второго аргумента требуется событие, которое мы собираемся прослушать, которое в данном случае click
и третий аргумент на самом деле является функцией обратного вызова, которую мы делаем с помощью функции стрелки.
this.renderer.listen('window', 'click',(e:Event)=>{ // your code here})
Остальное решение простое, вам просто нужно установить логический флаг, который сохраняет статус видимости меню (или панели), и нам нужно назначить false
на этот флаг, когда он нажал за пределами меню.
Вот ссылка на демо Stackblitz
HTML
<button #toggleButton (click)="toggleMenu()"> Toggle Menu</button>
<div class="menu" *ngIf="isMenuOpen" #menu>
I'm the menu. Click outside to close me
</div>
app.component.ts
export class AppComponent {
/**
* This is the toogle button elemenbt, look at HTML and see its defination
*/
@ViewChild('toggleButton') toggleButton: ElementRef;
@ViewChild('menu') menu: ElementRef;
constructor(private renderer: Renderer2) {
/**
* This events get called by all clicks on the page
*/
this.renderer.listen('window', 'click',(e:Event)=>{
/**
* Only run when toggleButton is not clicked
* If we don't check this, all clicks (even on the toggle button) gets into this
* section which in the result we might never see the menu open!
* And the menu itself is checked here, and it's where we check just outside of
* the menu and button the condition abbove must close the menu
*/
if(e.target !== this.toggleButton.nativeElement && e.target!==this.menu.nativeElement){
this.isMenuOpen=false;
}
});
}
isMenuOpen = false;
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
}
}
Вы можете сделать что-то вроде этого
@HostListener('document:mousedown', ['$event'])
onGlobalClick(event): void {
if (!this.elementRef.nativeElement.contains(event.target)) {
// clicked outside => close dropdown list
this.isOpen = false;
}
}
и используйте *ngIf=isOpen для панели
Я хотел бы добавить решение, которое помогло мне добиться правильного результата.
При использовании встроенных элементов, и вы хотите обнаружить щелчок по родительскому элементу, event.target дает ссылку на базовый дочерний элемент.
HTML
<div #toggleButton (click)="toggleMenu()">
<b>Toggle Menu</b>
<span class="some-icon"></span>
</div>
<div #menu class="menu" *ngIf="isMenuOpen">
<h1>I'm the menu.</h1>
<div>
I have some complex content containing multiple children.
<i>Click outside to close me</i>
</div>
</div>
Я нажимаю на текст "Переключить меню", event.target возвращает ссылку на элемент 'u' вместо #toggleButton div.
Для этого случая я использовал решение M98, включая Renderer2, но поменял условие на то, что было в ответе Суджая.
ToggleButton.nativeElement.contains (e.target) возвращает true, даже если цель события click находится в дочерних элементах nativeElement, что решает проблему.
component.ts
export class AppComponent {
/**
* This is the toogle button element, look at HTML and see its definition
*/
@ViewChild('toggleButton') toggleButton: ElementRef;
@ViewChild('menu') menu: ElementRef;
isMenuOpen = false;
constructor(private renderer: Renderer2) {
/**
* This events get called by all clicks on the page
*/
this.renderer.listen('window', 'click',(e:Event)=>{
/**
* Only run when toggleButton is not clicked
* If we don't check this, all clicks (even on the toggle button) gets into this
* section which in the result we might never see the menu open!
* And the menu itself is checked here, and it's where we check just outside of
* the menu and button the condition abbove must close the menu
*/
if(!this.toggleButton.nativeElement.contains(e.target) && !this.menu.nativeElement.contains(e.target)) {
this.isMenuOpen=false;
}
});
}
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
}
}
Мне нравится ответ Суджая. Если вы хотите вместо этого создать директиву (для использования в нескольких компонентах). Вот как бы я это сделал.
import {
Directive,
EventEmitter,
HostListener,
Output,
ElementRef,
} from '@angular/core';
@Directive({
selector: '[outsideClick]',
})
export class OutsideClickDirective {
@Output()
outsideClick: EventEmitter<MouseEvent> = new EventEmitter();
@HostListener('document:mousedown', ['$event'])
onClick(event: MouseEvent): void {
if (!this.elementRef.nativeElement.contains(event.target)) {
this.outsideClick.emit(event);
}
}
constructor(private elementRef: ElementRef) {}
}
Затем вы могли бы использовать эту директиву так:
<div class="menu" *ngIf="isMenuOpen" (outsideClick)="isMenuOpen = false" outsideClick #menu>
I'm the menu. Click outside to close me
</div>
Вот директива многоразового использования, она также охватывает случай, если элемент находится внутри ngIf:
import { Directive, ElementRef, Optional, Inject, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { DOCUMENT } from '@angular/common';
import { filter } from 'rxjs/operators';
@Directive({
selector: '[outsideClick]',
})
export class OutsideClickDirective implements OnInit, OnDestroy {
@Output('outsideClick') outsideClick = new EventEmitter<MouseEvent>();
private subscription: Subscription;
constructor(private element: ElementRef, @Optional() @Inject(DOCUMENT) private document: any) {}
ngOnInit() {
setTimeout(() => {
this.subscription = fromEvent<MouseEvent>(this.document, 'click')
.pipe(
filter(event => {
const clickTarget = event.target as HTMLElement;
return !this.isOrContainsClickTarget(this.element.nativeElement, clickTarget);
}),
)
.subscribe(event => this.outsideClick.emit());
}, 0);
}
private isOrContainsClickTarget(element: HTMLElement, clickTarget: HTMLElement) {
return element == clickTarget || element.contains(clickTarget);
}
ngOnDestroy() {
if (this.subscription) this.subscription.unsubscribe();
}
}
Кредиты на https://github.com/ngez/platform, я получил большую часть логики из этого.
Чего мне не хватало, так это setTimeout(..., 0), который гарантирует, что проверка будет запланирована после того, как компонент, использующий директиву, будет отрисован.
Полезные ссылки:
More Simplified Code with demo on: StackBlitz
I have made a common function to close the menu on outside click and Prevent the closeing if click triggered on specific elements.
HTML
<button (click)="toggleMenu(); preventCloseOnClick()">Toggle Menu</button>
<ul (click)="preventCloseOnClick()" *ngIf="menuOpen">
<li>Menu 1</li>
<li>Menu 2</li>
<li>Menu 3</li>
<li>Menu 4</li>
<li>Menu 5</li>
</ul>
TS
import { Component, VERSION, Renderer2 } from '@angular/core';
export class AppComponent {
menuOpen: boolean = false;
menuBtnClick: boolean = false;
constructor(private renderer: Renderer2) {
this.renderer.listen('window', 'click', (e: Event) => {
if (!this.menuBtnClick) {
this.menuOpen = false;
}
this.menuBtnClick = false;
});
}
toggleMenu() {
this.menuOpen = !this.menuOpen;
}
preventCloseOnClick() {
this.menuBtnClick = true;
}
}
спасибо Эмерика
ng-click-outside
работает отлично, это то, что мне было нужно, я тестировал свой модальный, но когда я нажимаю его, первый щелчок по кнопке, он убирает внешний щелчок, а затем не работал, чтобы установить модальный, но я добавил
delayClickOutsideInit="true"
из документов, и он работает очень хорошо, вот конечный результат:
<button
(click)="imageModal()"
>
<button/>
<div
*ngIf="isMenuOpen"
>
<div
(clickOutside)="onClickedOutside($event)"
delayClickOutsideInit="true"
>
Modal content
</div>
</div>
а это моя составляющая
import {
Component,
} from '@angular/core';
@Component({
selector: 'app-modal-header',
templateUrl: './modal-header.component.html',
styleUrls: ['./modal-header.component.css'],
})
export class ModalHeaderComponent implements OnInit {
public isMenuOpen = false;
constructor() {}
imageModal() {
this.isMenuOpen = !this.isMenuOpen;
}
closeModal() {
//you can do an only close function click
this.isMenuOpen = false;
}
onClickedOutside(e: Event) {
this.isMenuOpen = false;
}
}
Я сделал иначе, в отличие от предыдущих ответов.
я кладу mouseleave
, mouseenter
событие в раскрывающемся меню
<div
class="dropdown-filter"
(mouseleave)="onMouseOutFilter($event)"
(mouseenter)="onMouseEnterFilter($event)"
>
<ng-container *ngIf="dropdownVisible">
<input
type="text"
placeholder="search.."
class="form-control"
[(ngModel)]="keyword"
id="myInput"
(keyup)="onKeyUp($event)"
/>
</ng-container>
<ul
class="dropdown-content"
*ngIf="dropdownVisible"
>
<ng-container *ngFor="let item of filteredItems; let i = index">
<li
(click)="onClickItem($event, item)"
[ngStyle]="listWidth && {width: listWidth + 'px'}"
>
<span>{{ item.label }}</span>
</li>
</ng-container>
</ul>
</div>
constructor(private renderer: Renderer2) {
/**
* this.renderer instance would be shared with the other multiple same components
* so you should have one more flag to divide the components
* the only dropdown with mouseInFilter which is false should be close
*/
this.renderer.listen('document', 'click', (e: Event) => {
if (!this.mouseInFilter) {
// this is the time to hide dropdownVisible
this.dropdownVisible = false;
}
});
}
onMouseOutFilter(e) {
this.mouseInFilter = false;
}
onMouseEnterFilter(e) {
this.mouseInFilter = true;
}
и убедитесь, что значение defaultValue для mouseInFilter равно false;
ngOnInit() {
this.mouseInFilter = false;
this.dropdownVisible = false;
}
и когда раскрывающийся список должен быть виден, mouseInFilter будет истинным
toggleDropDownVisible() {
if (!this.dropdownVisible) {
this.mouseInFilter = true;
}
this.dropdownVisible = !this.dropdownVisible;
}
Вы можете использовать https://github.com/arkon/ng-click-outside, который довольно прост в использовании и имеет множество полезных функций:
@Component({
selector: 'app',
template: `
<div (clickOutside)="onClickedOutside($event)">Click outside this</div>
`
})
export class AppComponent {
onClickedOutside(e: Event) {
console.log('Clicked outside:', e);
}
}
Что касается производительности , библиотека использует
ngOnDestroy
чтобы удалить слушателя, когда директива неактивна (используйте
clickOutsideEnabled
property), что действительно важно, и большинство предлагаемых решений этого не делают. Смотрите исходный код здесь.
Я сделал то же самое в одном из моих требований, чтобы показывать всплывающее меню мегаменю, когда пользователь нажимает значок меню, но хочет закрыть его, чтобы пользователь щелкал вне его. Здесь я также пытаюсь предотвратить щелчок по значку. Пожалуйста, посмотрите.
В HTML
<div #menuIcon (click)="onMenuClick()">
<a><i class="fa fa-reorder"></i></a>
</div>
<div #menuPopup *ngIf="showContainer">
<!-- Something in the popup like menu -->
</div>
В ТС
@ViewChild('menuIcon', { read: ElementRef, static: false }) menuIcon: ElementRef;
@ViewChild('menuPopup', { read: ElementRef, static: false }) menuPopup: ElementRef;
showContainer = false;
constructor(private renderer2: Renderer2) {
this.renderer2.listen('window', 'click', (e: Event) => {
if (
(this.menuPopup && this.menuPopup.nativeElement.contains(e.target)) ||
(this.menuIcon && this.menuIcon.nativeElement.contains(e.target))
) {
// Clicked inside plus preventing click on icon
this.showContainer = true;
} else {
// Clicked outside
this.showContainer = false;
}
});
}
onMenuClick() {
this.isShowMegaMenu = true;
}
если click() и clickOutside() запускаются одновременно, вы должны обратиться
https://github.com/arkon/ng-click-outside/issues/31
это решило мою проблему