Respuestas:
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';
}
}
Una alternativa a la respuesta de AMagyar. Esta versión funciona cuando hace clic en el elemento que se elimina del 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;
}
La vinculación al clic del documento a través de @Hostlistener es costosa. Puede tener y tendrá un impacto visible en el rendimiento si lo abusa (por ejemplo, cuando crea un componente desplegable personalizado y tiene varias instancias creadas en un formulario).
Sugiero agregar un @Hostlistener () al evento de clic del documento solo una vez dentro de su componente principal de la aplicación. El evento debería empujar el valor del elemento objetivo en el que se hizo clic dentro de un sujeto público almacenado en un servicio de utilidad global.
@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>()
}
Quien esté interesado en el elemento de destino en el que se hizo clic debe suscribirse al tema público de nuestro servicio de utilidades y darse de baja cuando se destruya el componente.
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
}
Las respuestas mencionadas anteriormente son correctas, pero ¿qué sucede si está haciendo un proceso pesado después de perder el enfoque del componente relevante? Para eso, vine con una solución con dos banderas donde el proceso de evento de enfoque solo se llevará a cabo cuando se pierda el enfoque solo del componente relevante.
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;
}
Espero que esto te ayudará. Corrígeme si me he perdido algo.
Puede usar el métodoclickOutside () del paquete https://www.npmjs.com/package/ng-click-outside
La respuesta de ginalx debe establecerse como la predeterminada imo: este método permite muchas optimizaciones.
El problema
Digamos que tenemos una lista de elementos y que en cada elemento queremos incluir un menú que debe alternarse. Incluimos una palanca en un botón que escucha un click
evento en sí mismo (click)="toggle()"
, pero también queremos cambiar el menú cada vez que el usuario hace clic fuera de él. Si la lista de elementos crece y adjuntamos un @HostListener('document:click')
en cada menú, entonces cada menú cargado dentro del elemento comenzará a escuchar el clic en todo el documento, incluso cuando el menú está desactivado. Además de los problemas obvios de rendimiento, esto es innecesario.
Puede, por ejemplo, suscribirse cada vez que se activa la ventana emergente mediante un clic y comenzar a escuchar "clics externos" solo entonces.
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();
}
Y en *.component.html
:
<button (click)="toggle()">Toggle the menu</button>
tap
operador. En su lugar, utilice skipWhile(() => !this.isActive), switchMap(() => this._utilitiesService.documentClickedTarget), filter((target) => !this._elementRef.nativeElement.contains(target)), tap(() => this._toggleMenuSubject$.next(false))
. De esta manera, utiliza muchos más RxJ y omite algunas suscripciones.
Mejorando @J. Frankenstein responde
@HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}
@HostListener('document:click')
clickout() {
this.text = "clicked outside";
}
Puedes llamar a la función de evento como (focusout) o (desenfoque) y luego pones tu código
<div tabindex=0 (blur)="outsideClick()">raw data </div>
outsideClick() {
alert('put your condition here');
}