Me gustaría cerrar mi menú desplegable de inicio de sesión cuando el usuario haga clic en cualquier lugar fuera de ese menú desplegable, y me gustaría hacerlo con Angular2 y con el "enfoque" de Angular2 ...
He implementado una solución, pero realmente no me siento seguro con ella. Creo que debe haber una manera más fácil de lograr el mismo resultado, así que si tienes alguna idea ... ¡discutamos :)!
Aquí está mi implementación:
El componente desplegable:
Este es el componente para mi menú desplegable:
- Cada vez que este componente se establece en visible, (por ejemplo: cuando el usuario hace clic en un botón para mostrarlo) se suscribe a un sujeto de usuario "global" rxjs almacenado en el servicio de sujetos .
- Y cada vez que está oculto, se da de baja de este tema.
- Cada clic en cualquier lugar dentro de la plantilla de este componente activa el método onClick () , que simplemente detiene el burbujeo de eventos en la parte superior (y el componente de la aplicación)
Aqui esta el codigo
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;
}
}
El componente de la aplicación:
Por otro lado, está el componente de la aplicación (que es un padre del componente desplegable):
- Este componente captura cada evento de clic y emite en el mismo Asunto rxjs ( userMenu )
Aquí está el código:
export class AppComponent {
constructor( public subjects: SubjectsService) {
document.addEventListener('click', () => this.onClick());
}
onClick( ) {
this.subjects.userMenu.next({});
}
}
Lo que me molesta:
- No me siento realmente cómodo con la idea de tener un Sujeto global que actúe como conector entre esos componentes.
- El setTimeout : Esto es necesario porque esto es lo que sucede lo contrario, si el usuario pulsa en el botón que muestra el menú desplegable:
- El usuario hace clic en el botón (que no es parte del componente desplegable) para mostrar el menú desplegable.
- Se muestra el menú desplegable e inmediatamente se suscribe al asunto UserMenu .
- El evento de clic aparece en el componente de la aplicación y queda atrapado
- El componente de la aplicación emite un evento sobre el tema userMenu
- El componente desplegable captura esta acción en userMenu y oculta el menú desplegable.
- Al final, el menú desplegable nunca se muestra.
Este tiempo de espera establecido retrasa la suscripción al final del código JavaScript actual que resuelve el problema, pero en una forma muy elegante en mi opinión.
Si conoce soluciones más limpias, mejores, más inteligentes, más rápidas o más fuertes, ¡hágamelo saber :)!
fuente
Respuestas:
Puedes usar el
(document:click)
evento:Otro enfoque es crear un evento personalizado como directiva. Echa un vistazo a estas publicaciones de Ben Nadel:
fuente
@HostListener('document:click', ['$event'])
lugar de lahost
propiedad en elComponent
decorador.Observable.fromEvent(document, 'click').subscribe(event => {your code here})
por lo que siempre puede suscribirse solo cuando necesita escuchar, por ejemplo, abrió el menú desplegable, y cuando lo cierra se da de bajaMÉTODO ELEGANTE
Encontré esta
clickOut
directiva: https://github.com/chliebel/angular2-click-outside . Lo compruebo y funciona bien (solo copioclickOutside.directive.ts
a mi proyecto). U puede usarlo de esta manera:¿Dónde
close
está su función que se llamará cuando el usuario haga clic fuera de div. Es una forma muy elegante de manejar el problema descrito en cuestión.Si usa la directiva anterior para cerrar la ventana emergente, recuerde primero agregar
event.stopPropagation()
al controlador de eventos de clic de botón que abre la ventana emergente.PRIMA:
A continuación, copie el código de directiva original del archivo
clickOutside.directive.ts
(en caso de que el enlace deje de funcionar en el futuro), el autor es Christian Liebel :Mostrar fragmento de código
fuente
<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Lo he hecho de esta manera.
Se agregó un detector de eventos en el documento
click
y en ese controlador verificó si micontainer
contieneevent.target
, si no, oculte el menú desplegable.Se vería así.
fuente
this.offClickHandler
función de flecha.Creo que Sasxa aceptó que la respuesta funciona para la mayoría de las personas. Sin embargo, tuve una situación en la que el contenido del Elemento, que debería escuchar eventos fuera de clic, cambió dinámicamente. Por lo tanto, Elements nativeElement no contenía event.target, cuando se creó dinámicamente. Podría resolver esto con la siguiente directiva
En lugar de verificar si elementRef contiene event.target, verifico si elementRef está en la ruta (ruta DOM al destino) del evento. De esa manera es posible manejar Elementos creados dinámicamente.
fuente
Si está haciendo esto en iOS, use el
touchstart
evento también:A partir de Angular 4, la
HostListener
decoración es la forma preferida de hacer estofuente
Hemos estado trabajando en un problema similar en el trabajo hoy, tratando de descubrir cómo hacer que un div desplegable desaparezca cuando se desactiva. La nuestra es ligeramente diferente a la pregunta del póster inicial porque no queríamos alejarnos de un componente o directiva diferente , sino simplemente fuera del div particular.
Terminamos resolviéndolo usando el controlador de eventos (window: mouseup).
Pasos:
1.) Le dimos a todo el menú desplegable div un nombre de clase único.
2.) En el menú desplegable interno (la única parte en la que queríamos que los clics NO cerraran el menú), agregamos un controlador de eventos (ventana: mouseup) y pasamos el evento $.
NOTA: No se pudo hacer con un controlador de "clic" típico porque esto entraba en conflicto con el controlador de clic principal.
3.) En nuestro controlador, creamos el método al que queríamos que se nos llamara en el evento click out, y usamos event.closest ( docs here ) para averiguar si el punto cliqueado está dentro de nuestro div de clase objetivo.
fuente
.closest()
no es compatible con IE / Edge a partir de hoy ( caniuse )Puede crear un elemento hermano en el menú desplegable que cubra toda la pantalla que sería invisible y esté allí solo para capturar eventos de clic. Entonces podría detectar clics en ese elemento y cerrar el menú desplegable cuando se hace clic. Digamos que ese elemento es de serigrafía de clase, aquí hay algo de estilo para ello:
El índice z debe ser lo suficientemente alto como para colocarlo por encima de todo, excepto su menú desplegable. En este caso, mi menú desplegable sería b z-index 2.
Las otras respuestas funcionaron en algunos casos para mí, excepto que a veces mi menú desplegable se cerraba cuando interactuaba con elementos dentro de él y no quería eso. Agregué dinámicamente elementos que no estaban contenidos en mi componente, de acuerdo con el objetivo del evento, como esperaba. En lugar de resolver ese desastre, pensé que lo intentaría a la manera de serigrafía.
fuente
No hice ninguna solución. Acabo de adjuntar el documento: haga clic en mi función de alternar de la siguiente manera:
Entonces, cuando estoy fuera de mi directiva, cierro el menú desplegable.
fuente
DOWNNSIDES: - Escucha de eventos con dos clics para cada uno de estos componentes en la página. No use esto en componentes que están en la página cientos de veces.
fuente
Me gustaría complementar la respuesta de @Tony, ya que el evento no se elimina después del clic fuera del componente. Recibo completo:
Marque su elemento principal con #container
En el elemento en el que se puede hacer clic, use:
Ahora puede controlar su estado desplegable con la variable dropstatus y aplicar las clases adecuadas con [ngClass] ...
fuente
Puedes escribir la directiva:
En su componente:
Esta directiva emite un evento cuando el elemento html está contenido en DOM y cuando la propiedad de entrada [clickOut] es 'true'. Escucha el evento mousedown para manejar el evento antes de que el elemento se elimine del DOM.
Y una nota: firefox no contiene la propiedad 'ruta' en caso de que pueda usar la función para crear la ruta:
Por lo tanto, debe cambiar el controlador de eventos en la directiva: event.path debe reemplazarse getEventPath (event)
Este módulo puede ayudar. https://www.npmjs.com/package/ngx-clickout Contiene la misma lógica pero también maneja el evento esc en el elemento html de origen.
fuente
La respuesta correcta tiene un problema, si tiene un componente clicakble en su popover, el elemento ya no estará en el
contain
método y se cerrará, según @ JuHarm89 que creé el mío:¡Gracias por la ayuda!
fuente
Una mejor versión para la gran solución @Tony:
En un archivo css: // NO es necesario si usa el menú desplegable bootstrap.
fuente
Debería verificar si hace clic en la superposición modal en su lugar, mucho más fácil.
Su plantilla:
Y el método:
fuente
He elaborado una directiva para abordar este problema similar y estoy usando Bootstrap. Pero en mi caso, en lugar de esperar el evento de clic fuera del elemento para cerrar el menú desplegable abierto actual, creo que es mejor si vigilamos el evento 'mouseleave' para cerrar automáticamente el menú.
Aquí está mi solución:
Directiva
HTML
fuente
Si está utilizando Bootstrap, puede hacerlo directamente con bootstrap mediante menús desplegables (componente Bootstrap).
Ahora está bien poner
(click)="clickButton()"
cosas en el botón. http://getbootstrap.com/javascript/#dropdownsfuente
También hice una pequeña solución por mi cuenta.
Creé un evento (dropdownOpen) que escucho en mi componente de elemento ng-select y llamo a una función que cerrará todos los demás SelectComponent abiertos aparte del SelectComponent abierto actualmente.
Modifiqué una función dentro del archivo select.ts como a continuación para emitir el evento:
En el HTML agregué un detector de eventos para (dropdownOpened) :
Esta es mi función de llamada en el desencadenante de eventos dentro del componente que tiene la etiqueta ng2-select:
fuente
NOTA: Para aquellos que deseen utilizar trabajadores web y que necesiten evitar usar document y nativeElement, esto funcionará.
Respondí la misma pregunta aquí: /programming/47571144
Copiar / Pegar desde el enlace de arriba:
Tuve el mismo problema cuando estaba haciendo un menú desplegable y un diálogo de confirmación que quería descartarlos al hacer clic afuera.
Mi implementación final funciona perfectamente pero requiere algunas animaciones y estilos de css3.
NOTA : no he probado el siguiente código, puede haber algunos problemas de sintaxis que deben resolverse, ¡también los ajustes obvios para su propio proyecto!
Lo que hice:
Hice un div fijo separado con altura 100%, ancho 100% y transform: scale (0), esto es esencialmente el fondo, puedes peinarlo con background-color: rgba (0, 0, 0, 0.466); para hacer obvio que el menú está abierto y el fondo es hacer clic para cerrar. El menú obtiene un índice z más alto que todo lo demás, luego el div de fondo obtiene un índice z más bajo que el menú pero también más alto que todo lo demás. Luego, el fondo tiene un evento de clic que cierra el menú desplegable.
Aquí está con su código html.
Aquí está el css3 que necesita algunas animaciones simples.
Si no buscas nada visual, puedes usar una transición como esta
fuente
Encontré otra solución, inspirada en ejemplos con eventos de enfoque / desenfoque.
Por lo tanto, si desea lograr la misma funcionalidad sin adjuntar el escucha de documentos global, puede considerar como válido el siguiente ejemplo. Funciona también en Safari y Firefox en OSx, a pesar de que tienen otro manejo del evento de foco de botón: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
Ejemplo de trabajo en stackbiz con angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts
Marcado HTML:
La directiva se verá así:
fuente
Puede usar
mouseleave
en su vista asíPrueba con angular 8 y funciona perfectamente
fuente
EL MÉTODO MÁS ELEGANTE: D
Hay una manera más fácil de hacer eso, no necesita directivas para eso.
"element-that-toggle-your-dropdown" debe ser una etiqueta de botón. Utilice cualquier método en el atributo (desenfoque). Eso es todo.
fuente