Je voudrais fermer ma liste déroulante de menu de connexion lorsque l'utilisateur clique n'importe où en dehors de cette liste déroulante, et j'aimerais le faire avec Angular2 et avec "l'approche" Angular2 ...
J'ai mis en place une solution, mais je ne me sens vraiment pas en confiance. Je pense qu'il doit y avoir un moyen le plus simple d'obtenir le même résultat, donc si vous avez des idées ... discutons-en :)!
Voici ma mise en œuvre:
Le composant déroulant:
C'est le composant de ma liste déroulante:
- Chaque fois que ce composant est défini sur visible, (par exemple: lorsque l'utilisateur clique sur un bouton pour l'afficher), il s'abonne à un userMenu "global" sujet rxjs stocké dans le SubjectsService .
- Et chaque fois qu'il est caché, il se désabonne à ce sujet.
- Chaque clic n'importe où dans le modèle de ce composant déclenche la méthode onClick () , qui arrête simplement l'événement de remonter vers le haut (et le composant d'application)
Voici le code
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;
}
}
Le composant applicatif:
D'autre part, il y a le composant d'application (qui est un parent du composant déroulant):
- Ce composant capture chaque événement de clic et émet sur le même sujet rxjs ( userMenu )
Voici le code:
export class AppComponent {
constructor( public subjects: SubjectsService) {
document.addEventListener('click', () => this.onClick());
}
onClick( ) {
this.subjects.userMenu.next({});
}
}
Ce qui me dérange:
- Je ne me sens pas vraiment à l'aise avec l'idée d'avoir un sujet global qui fasse office de connecteur entre ces composants.
- Le setTimeout : Ceci est nécessaire car voici ce qui se passe sinon si l'utilisateur clique sur le bouton qui affiche la liste déroulante:
- L'utilisateur clique sur le bouton (qui ne fait pas partie du composant déroulant) pour afficher le menu déroulant.
- La liste déroulante s'affiche et il s'abonne immédiatement à l'objet userMenu .
- L'événement de clic s'incline jusqu'au composant de l'application et se fait prendre
- Le composant application émet un événement sur le sujet userMenu
- Le composant déroulant capture cette action sur userMenu et masque la liste déroulante.
- À la fin, la liste déroulante n'est jamais affichée.
Ce délai d'expiration retardé l'abonnement à la fin du tour de code JavaScript actuel qui résout le problème, mais d'une manière très élégante à mon avis.
Si vous connaissez des solutions plus propres, meilleures, plus intelligentes, plus rapides ou plus fortes, faites-le moi savoir :)!
la source
Réponses:
Vous pouvez utiliser l'
(document:click)
événement:Une autre approche consiste à créer un événement personnalisé en tant que directive. Découvrez ces articles de Ben Nadel:
la source
@HostListener('document:click', ['$event'])
place de lahost
propriété sur leComponent
décorateur.Observable.fromEvent(document, 'click').subscribe(event => {your code here})
, afin que vous puissiez toujours vous abonner uniquement lorsque vous avez besoin d'écouter, par exemple, vous avez ouvert le menu déroulant, et lorsque vous le fermez, vous vous désabonnezMÉTHODE ÉLÉGANTE
J'ai trouvé cette
clickOut
directive: https://github.com/chliebel/angular2-click-outside . Je le vérifie et cela fonctionne bien (je ne copie queclickOutside.directive.ts
dans mon projet). Vous pouvez l'utiliser de cette manière:Où
close
est votre fonction qui sera appelée lorsque l'utilisateur cliquera en dehors de div. C'est une manière très élégante de traiter le problème décrit en question.Si vous utilisez la directive ci-dessus pour fermer la fenêtre popUp, n'oubliez pas d'abord d'ajouter
event.stopPropagation()
au gestionnaire d'événements de clic de bouton qui ouvre popUp.PRIME:
Ci-dessous, je copie le code de directive oryginal à partir du fichier
clickOutside.directive.ts
(au cas où le lien cesserait de fonctionner à l'avenir) - l'auteur est Christian Liebel :Afficher l'extrait de code
la source
<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Je l'ai fait de cette façon.
Ajout d'un écouteur d'événements sur le document
click
et dans ce gestionnaire vérifié si mycontainer
contientevent.target
, sinon - cachez la liste déroulante.Cela ressemblerait à ceci.
la source
this.offClickHandler
fonction de flèche.Je pense que la réponse acceptée par Sasxa fonctionne pour la plupart des gens. Cependant, j'ai eu une situation où le contenu de l'élément, qui devrait écouter les événements hors clic, a changé de manière dynamique. Ainsi, les éléments nativeElement ne contenaient pas le event.target, lors de sa création dynamique. Je pourrais résoudre cela avec la directive suivante
Au lieu de vérifier si elementRef contient event.target, je vérifie si elementRef est dans le chemin (chemin DOM vers la cible) de l'événement. De cette façon, il est possible de gérer des éléments créés dynamiquement.
la source
Si vous faites cela sur iOS, utilisez le
touchstart
événement:À partir de Angular 4, la
HostListener
décoration est le moyen préféré de le fairela source
Nous avons travaillé sur un problème similaire au travail aujourd'hui, en essayant de comprendre comment faire disparaître un div déroulant lorsque vous cliquez dessus. La nôtre est légèrement différente de la question de l'affiche initiale parce que nous ne voulions pas cliquer loin d'un composant ou d'une directive différent , mais simplement en dehors de la div particulière.
Nous avons fini par le résoudre en utilisant le gestionnaire d'événements (window: mouseup).
Étapes:
1.) Nous avons donné à l'ensemble du menu déroulant div un nom de classe unique.
2.) Sur le menu déroulant interne lui-même (la seule partie sur laquelle nous voulions que les clics ne ferme PAS le menu), nous avons ajouté un gestionnaire d'événements (window: mouseup) et avons passé l'événement $.
REMARQUE: il n'a pas pu être effectué avec un gestionnaire de «clic» typique car cela était en conflit avec le gestionnaire de clic parent.
3.) Dans notre contrôleur, nous avons créé la méthode que nous voulions appeler lors de l'événement de clic, et nous utilisons event.closest ( docs ici ) pour savoir si l'endroit cliqué est dans notre div de classe ciblée.
la source
.closest()
n'est pas pris en charge sur IE / Edge à partir d'aujourd'hui ( caniuse )Vous pouvez créer un élément frère dans la liste déroulante qui couvre la totalité de l'écran qui serait invisible et ne serait là que pour capturer les événements de clic. Ensuite, vous pouvez détecter les clics sur cet élément et fermer la liste déroulante lorsque vous cliquez dessus. Disons que l'élément est de la sérigraphie de classe, voici un style pour cela:
Le z-index doit être suffisamment élevé pour le positionner au-dessus de tout sauf de votre liste déroulante. Dans ce cas, ma liste déroulante serait b z-index 2.
Les autres réponses ont fonctionné dans certains cas pour moi, sauf parfois ma liste déroulante se fermait lorsque j'interagissais avec des éléments à l'intérieur et je ne voulais pas cela. J'avais ajouté dynamiquement des éléments qui n'étaient pas contenus dans mon composant, en fonction de la cible de l'événement, comme je m'y attendais. Plutôt que de trier ce gâchis, je me suis dit que je l'essayerais simplement en sérigraphie.
la source
Je n'ai fait aucune solution de contournement. Je viens de joindre le document: cliquez sur ma fonction de bascule comme suit:
Ainsi, quand je suis en dehors de ma directive, je ferme la liste déroulante.
la source
INCONVÉNIENTS: - Deux écouteurs d'événement de clic pour chacun de ces composants sur la page. Ne l'utilisez pas sur des composants qui sont sur la page des centaines de fois.
la source
Je voudrais compléter la réponse @Tony, car l'événement n'est pas supprimé après le clic en dehors du composant. Reçu complet:
Marquez votre élément principal avec #container
Sur l'élément cliquable, utilisez:
Vous pouvez maintenant contrôler l'état de votre liste déroulante avec la variable dropstatus et appliquer les classes appropriées avec [ngClass] ...
la source
Vous pouvez écrire une directive:
Dans votre composant:
Cette directive émet un événement lorsque l'élément html contient dans DOM et lorsque la propriété d'entrée [clickOut] est "true". Il écoute l'événement mousedown pour gérer l'événement avant que l'élément ne soit supprimé du DOM.
Et une note: Firefox ne contient pas la propriété 'chemin' sur l'événement, vous pouvez utiliser la fonction pour créer le chemin:
Vous devez donc changer le gestionnaire d'événements sur la directive: event.path doit être remplacé getEventPath (event)
Ce module peut vous aider. https://www.npmjs.com/package/ngx-clickout Il contient la même logique mais gère également l'événement esc sur l'élément html source.
la source
La bonne réponse a un problème, si vous avez un composant clicakble dans votre popover, l'élément ne sera plus sur la
contain
méthode et se fermera, basé sur @ JuHarm89 j'ai créé le mien:Merci pour l'aide!
la source
Une meilleure version pour l'excellente solution @Tony:
Dans un fichier css: // PAS nécessaire si vous utilisez la liste déroulante bootstrap.
la source
Vous devriez vérifier si vous cliquez sur la superposition modale à la place, beaucoup plus facilement.
Votre modèle:
Et la méthode:
la source
J'ai fait une directive pour résoudre ce problème similaire et j'utilise Bootstrap. Mais dans mon cas, au lieu d'attendre l'événement de clic en dehors de l'élément pour fermer le menu déroulant actuellement ouvert, je pense qu'il est préférable de surveiller l'événement 'mouseleave' pour fermer automatiquement le menu.
Voici ma solution:
Directif
HTML
la source
Si vous utilisez Bootstrap, vous pouvez le faire directement avec bootstrap via des listes déroulantes (composant Bootstrap).
Maintenant, vous pouvez mettre des
(click)="clickButton()"
éléments sur le bouton. http://getbootstrap.com/javascript/#dropdownsla source
J'ai également fait une petite solution de contournement par moi-même.
J'ai créé un (dropdownOpen) événement que j'écoute dans mon composant d'élément ng-select et j'appelle une fonction qui fermera tous les autres SelectComponent ouverts à l'exception du SelectComponent actuellement ouvert.
J'ai modifié une fonction dans le fichier select.ts comme ci-dessous pour émettre l'événement:
Dans le HTML, j'ai ajouté un écouteur d'événements pour (dropdownOpened) :
C'est ma fonction d'appel sur le déclencheur d'événement à l'intérieur du composant ayant la balise ng2-select:
la source
REMARQUE: pour ceux qui souhaitent utiliser des travailleurs Web et que vous devez éviter d'utiliser document et nativeElement, cela fonctionnera.
J'ai répondu à la même question ici: /programming/47571144
Copiez / collez à partir du lien ci-dessus:
J'ai eu le même problème lorsque je faisais un menu déroulant et une boîte de dialogue de confirmation que je voulais les ignorer en cliquant à l'extérieur.
Ma mise en œuvre finale fonctionne parfaitement mais nécessite des animations et un style css3.
REMARQUE : je n'ai pas testé le code ci-dessous, il peut y avoir des problèmes de syntaxe à régler, ainsi que des ajustements évidents pour votre propre projet!
Ce que j'ai fait:
J'ai créé un div fixe séparé avec une hauteur de 100%, une largeur de 100% et une transformation: scale (0), c'est essentiellement l'arrière-plan, vous pouvez le styliser avec background-color: rgba (0, 0, 0, 0.466); pour rendre évident le menu est ouvert et l'arrière-plan est un clic pour fermer. Le menu obtient un z-index plus élevé que tout le reste, puis le div d'arrière-plan obtient un z-index inférieur au menu mais également supérieur à tout le reste. Ensuite, l'arrière-plan a un événement de clic qui ferme la liste déroulante.
Le voici avec votre code html.
Voici le css3 qui a besoin de quelques animations simples.
Si vous ne recherchez rien de visuel, vous pouvez simplement utiliser une transition comme celle-ci
la source
Je suis tombé sur une autre solution, inspirée d'exemples avec événement focus / flou.
Ainsi, si vous souhaitez obtenir les mêmes fonctionnalités sans attacher un écouteur de document global, vous pouvez considérer comme valide l'exemple suivant. Cela fonctionne également dans Safari et Firefox sur OSx, malgré une autre gestion de l'événement de focus de bouton: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
Exemple de travail sur stackbiz avec angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts
Balisage HTML:
La directive ressemblera à ceci:
la source
Vous pouvez utiliser
mouseleave
dans votre vue comme ceciTestez avec angular 8 et travaillez parfaitement
la source
LA MÉTHODE LA PLUS ÉLÉGANTE: D
Il existe un moyen le plus simple de le faire, pas besoin de directives pour cela.
"element-that-toggle-your-dropdown" doit être une balise button. Utilisez n'importe quelle méthode dans l'attribut (flou). C'est tout.
la source