Відповіді:
import { Component, ElementRef, HostListener, Input } from '@angular/core';
@Component({
selector: 'selector',
template: `
<div>
{{text}}
</div>
`
})
export class AnotherComponent {
public text: String;
@HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.text = "clicked inside";
} else {
this.text = "clicked outside";
}
}
constructor(private eRef: ElementRef) {
this.text = 'no clicks yet';
}
}
Альтернатива відповіді Амадьяра. Ця версія працює при натисканні на елемент, який видаляється з DOM за допомогою ngIf.
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
private wasInside = false;
@HostListener('click')
clickInside() {
this.text = "clicked inside";
this.wasInside = true;
}
@HostListener('document:click')
clickout() {
if (!this.wasInside) {
this.text = "clicked outside";
}
this.wasInside = false;
}
Прив’язка до натискання документа через @Hostlistener дорога. Це може і матиме видимий вплив на ефективність при надмірному використанні (наприклад, при створенні спеціального компонента, що випадає, і у вас створено кілька екземплярів у формі).
Я пропоную додавати @Hostlistener () до події клацання документа лише один раз у вашому головному компоненті додатка. Подія повинна підштовхнути значення клацнутого цільового елемента всередині загальнодоступного предмета, що зберігається в глобальній службі утиліти.
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(private utilitiesService: UtilitiesService) {}
@HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.utilitiesService.documentClickedTarget.next(event.target)
}
}
@Injectable({ providedIn: 'root' })
export class UtilitiesService {
documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}
Хто зацікавлений у вибраному цільовому елементі, повинен підписатися на загальнодоступний предмет нашої служби комунальних послуг та скасувати підписку, коли компонент знищений.
export class AnotherComponent implements OnInit {
@ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef
constructor(private utilitiesService: UtilitiesService) { }
ngOnInit() {
this.utilitiesService.documentClickedTarget
.subscribe(target => this.documentClickListener(target))
}
documentClickListener(target: any): void {
if (this.somePopup.nativeElement.contains(target))
// Clicked inside
else
// Clicked outside
}
Наведені вище відповіді правильні, але що робити, якщо ви робите важкий процес після втрати уваги від відповідного компонента. Для цього я запропонував рішення з двома прапорами, де процес події фокусування буде відбуватися лише при втраті фокусу лише від відповідного компонента.
isFocusInsideComponent = false;
isComponentClicked = false;
@HostListener('click')
clickInside() {
this.isFocusInsideComponent = true;
this.isComponentClicked = true;
}
@HostListener('document:click')
clickout() {
if (!this.isFocusInsideComponent && this.isComponentClicked) {
// do the heavy process
this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}
Сподіваюся, що це вам допоможе. Виправте мене, якщо щось пропустили.
Ви можете використовувати методclickOutside () з https://www.npmjs.com/package/ng-click-outside пакета
Відповідь ginalx має бути встановлена за замовчуванням: imo: цей метод дозволяє здійснити багато оптимізацій.
Проблема
Скажіть, що у нас є список елементів, і до кожного пункту ми хочемо включити меню, яке потрібно змінити. Ми включаємо кнопку перемикання на кнопку, яка слухає click
подію на собі (click)="toggle()"
, але ми також хочемо перемикати меню кожного разу, коли користувач натискає його. Якщо список елементів зростає, і ми додаємо до @HostListener('document:click')
кожного меню, то кожне завантажене в ньому меню почне прослуховувати клацання по всьому документу, навіть коли меню вимкнено. Окрім очевидних питань щодо ефективності, це зайве.
Наприклад, ви можете підписатися щоразу, коли спливаюче вікно перемикається за допомогою клацання, і тільки потім починати прослуховувати "зовнішні кліки".
isActive: boolean = false;
// to prevent memory leaks and improve efficiency, the menu
// gets loaded only when the toggle gets clicked
private _toggleMenuSubject$: BehaviorSubject<boolean>;
private _toggleMenu$: Observable<boolean>;
private _toggleMenuSub: Subscription;
private _clickSub: Subscription = null;
constructor(
...
private _utilitiesService: UtilitiesService,
private _elementRef: ElementRef,
){
...
this._toggleMenuSubject$ = new BehaviorSubject(false);
this._toggleMenu$ = this._toggleMenuSubject$.asObservable();
}
ngOnInit() {
this._toggleMenuSub = this._toggleMenu$.pipe(
tap(isActive => {
logger.debug('Label Menu is active', isActive)
this.isActive = isActive;
// subscribe to the click event only if the menu is Active
// otherwise unsubscribe and save memory
if(isActive === true){
this._clickSub = this._utilitiesService.documentClickedTarget
.subscribe(target => this._documentClickListener(target));
}else if(isActive === false && this._clickSub !== null){
this._clickSub.unsubscribe();
}
}),
// other observable logic
...
).subscribe();
}
toggle() {
this._toggleMenuSubject$.next(!this.isActive);
}
private _documentClickListener(targetElement: HTMLElement): void {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this._toggleMenuSubject$.next(false);
}
}
ngOnDestroy(){
this._toggleMenuSub.unsubscribe();
}
І, в *.component.html
:
<button (click)="toggle()">Toggle the menu</button>
tap
операторі. Натомість використовуйте skipWhile(() => !this.isActive), switchMap(() => this._utilitiesService.documentClickedTarget), filter((target) => !this._elementRef.nativeElement.contains(target)), tap(() => this._toggleMenuSubject$.next(false))
. Таким чином ви використовуєте набагато більше RxJ і пропускаєте деякі підписки.
Поліпшення @J. Франкенштейн
@HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}
@HostListener('document:click')
clickout() {
this.text = "clicked outside";
}
Ви можете викликати функцію події, як-от (фокус) або (розмиття), а потім поставити код
<div tabindex=0 (blur)="outsideClick()">raw data </div>
outsideClick() {
alert('put your condition here');
}