Risposte:
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';
}
}
Un'alternativa alla risposta di AMagyar. Questa versione funziona quando fai clic su un elemento che viene rimosso dal DOM con un 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;
}
Vincolare al clic del documento tramite @Hostlistener è costoso. Può e avrà un impatto visibile sulle prestazioni se si utilizza in modo eccessivo (ad esempio, quando si crea un componente a discesa personalizzato e si hanno più istanze create in un modulo).
Suggerisco di aggiungere un @Hostlistener () all'evento di clic del documento solo una volta all'interno del componente principale dell'app. L'evento dovrebbe spingere il valore dell'elemento target cliccato all'interno di un soggetto pubblico memorizzato in un servizio di utilità globale.
@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>()
}
Chiunque sia interessato all'elemento di destinazione cliccato deve iscriversi all'argomento pubblico del nostro servizio di utilità e annullare l'iscrizione quando il componente viene distrutto.
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
}
Le risposte sopra menzionate sono corrette, ma cosa succede se stai eseguendo un processo pesante dopo aver perso l'attenzione dal componente pertinente. Per questo, ho fornito una soluzione con due flag in cui il processo dell'evento focus out avrà luogo solo quando si perde il focus solo dal componente rilevante.
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;
}
Spero che questo ti possa aiutare. Correggimi se hai perso qualcosa.
Puoi usare il metodo clickOutside () da https://www.npmjs.com/package/ng-click-outside package
la risposta di ginalx dovrebbe essere impostata come quella predefinita imo: questo metodo consente molte ottimizzazioni.
Il problema
Diciamo che abbiamo un elenco di elementi e su ogni elemento vogliamo includere un menu che deve essere attivato. Includiamo un interruttore su un pulsante che ascolta un click
evento su se stesso (click)="toggle()"
, ma vogliamo anche attivare il menu ogni volta che l'utente fa clic al di fuori di esso. Se l'elenco degli elementi cresce e alleghiamo una @HostListener('document:click')
su ogni menu, allora ogni menu caricato all'interno dell'elemento inizierà ad ascoltare il clic sull'intero documento, anche quando il menu è disattivato. Oltre agli ovvi problemi di prestazioni, questo non è necessario.
Puoi, ad esempio, iscriverti ogni volta che il popup viene attivato tramite un clic e iniziare ad ascoltare i "clic esterni" solo allora.
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();
}
E, in *.component.html
:
<button (click)="toggle()">Toggle the menu</button>
tap
operatore. Invece, usa skipWhile(() => !this.isActive), switchMap(() => this._utilitiesService.documentClickedTarget), filter((target) => !this._elementRef.nativeElement.contains(target)), tap(() => this._toggleMenuSubject$.next(false))
. In questo modo utilizzi molti più RxJ e salti alcuni abbonamenti.
Migliorare @J. Frankenstein risponde
@HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}
@HostListener('document:click')
clickout() {
this.text = "clicked outside";
}
puoi chiamare la funzione evento come (focusout) o (blur) quindi inserisci il tuo codice
<div tabindex=0 (blur)="outsideClick()">raw data </div>
outsideClick() {
alert('put your condition here');
}