Gostaria de fechar meu menu suspenso de login quando o usuário clicar em qualquer lugar fora desse menu suspenso e gostaria de fazer isso com o Angular2 e com a "abordagem" do Angular2 ...
Eu implementei uma solução, mas realmente não me sinto confiante com ela. Eu acho que deve haver uma maneira mais fácil de obter o mesmo resultado, então se você tiver alguma idéia ... vamos discutir :)!
Aqui está a minha implementação:
O componente suspenso:
Este é o componente para o meu menu suspenso:
- Toda vez que este componente que definir a visível, (Por exemplo: quando o usuário clique em um botão para exibi-lo) que assinar um assunto "global" rxjs usermenu armazenado dentro do SubjectsService .
- E toda vez que está oculto, cancela a inscrição neste assunto.
- Cada qualquer clique dentro do modelo deste gatilho componente do onClick () método, que apenas parada evento bolha ao topo (e o componente de aplicação)
Aqui está o código
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;
}
}
O componente do aplicativo:
Por outro lado, existe o componente de aplicativo (que é o pai do componente suspenso):
- Esse componente captura todos os eventos de clique e emite no mesmo assunto rxjs ( userMenu )
Aqui está o código:
export class AppComponent {
constructor( public subjects: SubjectsService) {
document.addEventListener('click', () => this.onClick());
}
onClick( ) {
this.subjects.userMenu.next({});
}
}
O que me incomoda:
- Não me sinto muito à vontade com a ideia de ter um Assunto global que atue como o conector entre esses componentes.
- O setTimeout : Isso é necessário, porque é o que acontece caso contrário, se o usuário clicar no botão que mostra o menu suspenso:
- O usuário clica no botão (que não faz parte do componente suspenso) para mostrar o menu suspenso.
- O menu suspenso é exibido e ele se inscreve imediatamente no assunto userMenu .
- O evento click clica no componente do aplicativo e é capturado
- O componente de aplicativo emite um evento no assunto userMenu
- O componente suspenso captura essa ação no userMenu e oculta o menu suspenso.
- No final, o menu suspenso nunca é exibido.
Esse tempo limite definido atrasa a assinatura até o final do código JavaScript atual, que resolve o problema, mas de uma maneira muito elegante na minha opinião.
Se você souber soluções mais limpas, melhores, mais inteligentes, mais rápidas ou mais fortes, entre em contato :)!
fonte
Respostas:
Você pode usar o
(document:click)
evento:Outra abordagem é criar evento personalizado como uma diretiva. Confira estes posts de Ben Nadel:
fonte
@HostListener('document:click', ['$event'])
vez dehost
propriedade noComponent
decorador.Observable.fromEvent(document, 'click').subscribe(event => {your code here})
, assim você sempre pode se inscrever somente quando você precisa ouvir, por exemplo, você abriu suspensa, e quando você fechá-lo você BaixaMÉTODO ELEGANTE
Encontrei esta
clickOut
diretiva: https://github.com/chliebel/angular2-click-outside . Verifico e funciona bem (apenas copioclickOutside.directive.ts
para o meu projeto). Você pode usá-lo desta maneira:Onde
close
está sua função que será chamada quando o usuário clicar fora de div. É uma maneira muito elegante de lidar com o problema descrito em questão.Se você usar a diretiva acima para fechar a janela popUp, lembre-se de adicionar o
event.stopPropagation()
manipulador de eventos ao clique no botão que abre o popUp.BÔNUS:
Abaixo, copio o código da diretiva original do arquivo
clickOutside.directive.ts
(caso o link pare de funcionar no futuro) - o autor é Christian Liebel :Mostrar snippet de código
fonte
<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Eu fiz dessa maneira.
Adicionado um ouvinte de evento no documento
click
e nesse manipulador verificado se o meucontainer
contémevent.target
, se não - ocultar o menu suspenso.Seria assim.
fonte
this.offClickHandler
função de seta.Acho que Sasxa aceitou resposta funciona para a maioria das pessoas. No entanto, tive uma situação em que o conteúdo do elemento, que deveria ouvir eventos fora do clique, mudou dinamicamente. Portanto, o Elements nativeElement não continha o event.target, quando foi criado dinamicamente. Eu poderia resolver isso com a seguinte diretiva
Em vez de verificar se elementRef contém event.target, verifico se elementRef está no caminho (caminho DOM para o destino) do evento. Dessa forma, é possível manipular elementos criados dinamicamente.
fonte
Se você estiver fazendo isso no iOS, use o
touchstart
evento, bem como:A partir do Angular 4, a
HostListener
decoração é a maneira preferida de fazer issofonte
Estamos trabalhando em um problema semelhante no trabalho hoje, tentando descobrir como fazer com que uma div suspensa desapareça quando ela é clicada. A nossa é um pouco diferente da pergunta do pôster inicial, porque não queremos clicar fora de um componente ou diretiva diferente , mas apenas fora da div específica.
Acabamos resolvendo isso usando o manipulador de eventos (window: mouseup).
Etapas:
1.) Atribuímos a todo o menu suspenso um nome de classe exclusivo.
2.) No próprio menu suspenso (a única parte em que desejamos clicar para NÃO fechar o menu), adicionamos um manipulador de eventos (window: mouseup) e passamos o evento $.
NOTA: Não foi possível fazer isso com um manipulador de "clique" típico, porque isso entrava em conflito com o manipulador de clique pai.
3.) Em nosso controller, criamos o método que queríamos chamar no evento click out e usamos o event.closest ( documentos aqui ) para descobrir se o ponto clicado está dentro da nossa classe de destino.
fonte
.closest()
não é suportada no IE / Borda a partir de hoje ( caniuse )Você pode criar um elemento irmão no menu suspenso que cubra a tela inteira que seria invisível e estaria lá apenas para capturar eventos de clique. Em seguida, você pode detectar cliques nesse elemento e fechar a lista suspensa quando é clicada. Digamos que esse elemento seja da classe silkscreen, aqui está um estilo para ele:
O índice z precisa ser alto o suficiente para posicioná-lo acima de tudo, exceto do menu suspenso. Nesse caso, meu menu suspenso seria z-index 2.
As outras respostas funcionaram em alguns casos para mim, exceto que, às vezes, meu menu suspenso fechava quando eu interagia com elementos dentro dele e não queria isso. Eu adicionei dinamicamente elementos que não estavam contidos no meu componente, de acordo com o destino do evento, como eu esperava. Em vez de resolver essa bagunça, imaginei que tentaria da maneira serigráfica.
fonte
Eu não fiz nenhuma solução alternativa. Acabei de anexar o documento: clique na minha função de alternância da seguinte forma:
Então, quando estou fora da minha diretiva, fecho a lista suspensa.
fonte
DOWNSIDES: - Dois ouvintes de eventos de clique para cada um desses componentes na página. Não use isso em componentes que estão na página centenas de vezes.
fonte
Gostaria de complementar a resposta do @Tony, pois o evento não está sendo removido após o clique fora do componente. Recibo completo:
Marque seu elemento principal com #container
No elemento clicável, use:
Agora você pode controlar seu estado suspenso com a variável dropstatus e aplicar classes apropriadas com [ngClass] ...
fonte
Você pode escrever a diretiva:
No seu componente:
Esta diretiva emite um evento quando o elemento html está contido no DOM e quando a propriedade de entrada [clickOut] é 'true'. Ele escuta o evento do mouse para manipular o evento antes que o elemento seja removido do DOM.
E uma observação: o Firefox não contém a propriedade 'caminho' no evento, você pode usar a função para criar o caminho:
Portanto, você deve alterar o manipulador de eventos na diretiva: event.path deve ser substituído getEventPath (event)
Este módulo pode ajudar. https://www.npmjs.com/package/ngx-clickout Ele contém a mesma lógica, mas também manipula o evento esc no elemento html de origem.
fonte
A resposta correta tem um problema: se você tiver um componente clicakble na sua popover, o elemento não estará mais no
contain
método e será fechado, com base no @ JuHarm89 que criei o meu:Obrigado pela ajuda!
fonte
Uma versão melhor para a ótima solução @Tony:
Em um arquivo css: // NÃO é necessário se você usar o menu suspenso de auto-inicialização.
fonte
Você deve verificar se clica na sobreposição modal, muito mais fácil.
Seu modelo:
E o método:
fonte
Eu fiz uma diretiva para resolver esse problema semelhante e estou usando o Bootstrap. Mas, no meu caso, em vez de aguardar o evento click fora do elemento para fechar o menu suspenso aberto atual, acho melhor se observarmos o evento 'mouseleave' para fechar automaticamente o menu.
Aqui está a minha solução:
Directiva
HTML
fonte
Se você estiver usando o Bootstrap, poderá fazê-lo diretamente com o modo Bootstrap através de menus suspensos (componente Bootstrap).
Agora não há problema em colocar
(click)="clickButton()"
coisas no botão. http://getbootstrap.com/javascript/#dropdownsfonte
Eu também fiz uma pequena solução alternativa.
Criei um evento (dropdownOpen) que escuto no meu componente de elemento ng-select e chamo uma função que fechará todos os outros SelectComponent's abertos, exceto o SelectComponent aberto no momento.
Modifiquei uma função dentro do arquivo select.ts como abaixo para emitir o evento:
No HTML, adicionei um ouvinte de evento para (dropdownOpened) :
Esta é minha função de chamada no acionador de eventos dentro do componente que possui a tag ng2-select:
fonte
NOTA: Para aqueles que desejam usar trabalhadores da Web e você precisa evitar o uso de documentos e elementos nativos, isso funcionará.
Respondi à mesma pergunta aqui: /programming/47571144
Copie / cole no link acima:
Eu tive o mesmo problema quando criava um menu suspenso e uma caixa de diálogo de confirmação que queria descartá-los ao clicar fora.
Minha implementação final funciona perfeitamente, mas requer algumas animações e estilo do CSS3.
NOTA : não testei o código abaixo; pode haver alguns problemas de sintaxe que precisam ser resolvidos, além dos ajustes óbvios para o seu próprio projeto!
O que eu fiz:
Eu fiz uma div fixa separada com altura 100%, largura 100% e transformação: scale (0), este é essencialmente o plano de fundo, você pode estilizá-lo com a cor de fundo: rgba (0, 0, 0, 0,466); para tornar óbvio, o menu é aberto e o plano de fundo é clicado para fechar. O menu obtém um índice z mais alto do que tudo o resto, então a div background obtém um índice z mais baixo do que o menu, mas também mais alto do que tudo o resto. Em seguida, o plano de fundo possui um evento de clique que fecha a lista suspensa.
Aqui está o seu código html.
Aqui está o css3 que precisa de algumas animações simples.
Se você não está procurando nada visual, basta usar uma transição como esta
fonte
Me deparei com outra solução, inspirada em exemplos com evento de foco / desfoque.
Portanto, se você deseja obter a mesma funcionalidade sem anexar o ouvinte de documento global, considere válido o exemplo a seguir. Funciona também no Safari e Firefox no OSx, apesar de terem outras formas de manipulação do evento de foco de botão: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
Exemplo de trabalho no stackbiz com angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts
Marcação HTML:
A diretiva ficará assim:
fonte
Você pode usar
mouseleave
na sua opinião assimTeste com angular 8 e trabalhe perfeitamente
fonte
O MÉTODO MAIS ELEGANTE: D
Existe uma maneira mais fácil de fazer isso, sem necessidade de diretrizes para isso.
"elemento que alterna seu menu suspenso" deve ser a tag do botão. Use qualquer método no atributo (desfoque). Isso é tudo.
fonte