Chciałbym zamknąć menu logowania, gdy użytkownik kliknie gdziekolwiek poza tym menu i chciałbym to zrobić z Angular2 i „podejściem” Angular2 ...
Wdrożyłem rozwiązanie, ale naprawdę nie czuję się z nim pewnie. Myślę, że musi być najłatwiejszy sposób na osiągnięcie tego samego rezultatu, więc jeśli masz jakieś pomysły ... porozmawiajmy :)!
Oto moja realizacja:
Składnik rozwijany:
To jest składnik mojego menu:
- Za każdym razem, gdy ten komponent jest ustawiony jako widoczny (na przykład: gdy użytkownik kliknie przycisk, aby go wyświetlić), subskrybuje „globalne” menu użytkownika tematu rxjs przechowywane w usłudze SubjectsService .
- I za każdym razem, gdy jest ukryty, wypisuje się z tego tematu.
- Każde kliknięcie w dowolnym miejscu w szablonie tego komponentu wyzwala metodę onClick () , która po prostu zatrzymuje bulgotanie zdarzeń na górę (i komponent aplikacji)
Oto kod
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;
}
}
Składnik aplikacji:
Z drugiej strony istnieje komponent aplikacji (który jest rodzicem komponentu rozwijanego):
- Ten komponent przechwytuje każde zdarzenie kliknięcia i emituje na tym samym temacie rxjs ( userMenu )
Oto kod:
export class AppComponent {
constructor( public subjects: SubjectsService) {
document.addEventListener('click', () => this.onClick());
}
onClick( ) {
this.subjects.userMenu.next({});
}
}
Co mi przeszkadza:
- Nie czuję się komfortowo z pomysłem posiadania globalnego podmiotu, który działa jako łącznik między tymi komponentami.
- SetTimeout : Jest to konieczne, ponieważ tutaj jest to, co się stało inaczej, gdy użytkownik kliknie na przycisk, który pokazuje listę rozwijaną:
- Użytkownik klika przycisk (który nie jest częścią rozwijanego komponentu), aby wyświetlić listę rozwijaną.
- Zostanie wyświetlone menu rozwijane, które natychmiast zasubskrybuje temat menu użytkownika .
- Zdarzenie kliknięcia pojawia się w dymku do komponentu aplikacji i zostaje złapane
- Komponent aplikacji emituje zdarzenie w temacie userMenu
- Komponent listy rozwijanej przechwytuje tę akcję w userMenu i ukrywa listę rozwijaną.
- Na końcu lista rozwijana nigdy nie jest wyświetlana.
Ten ustawiony limit czasu opóźnia subskrypcję do końca bieżącej tury kodu JavaScript, co rozwiązuje problem, ale moim zdaniem w bardzo elegancki sposób.
Jeśli znasz czystsze, lepsze, mądrzejsze, szybsze lub mocniejsze rozwiązania, daj mi znać :)!
javascript
drop-down-menu
angular
rxjs
Łaskawy
źródło
źródło
Odpowiedzi:
Możesz użyć
(document:click)
wydarzenia:Innym podejściem jest utworzenie zdarzenia niestandardowego jako dyrektywy. Sprawdź te posty Bena Nadela:
źródło
@HostListener('document:click', ['$event'])
zamiasthost
właściwościComponent
.Observable.fromEvent(document, 'click').subscribe(event => {your code here})
, więc zawsze możesz subskrybować tylko wtedy, gdy chcesz słuchać, na przykład otworzyłeś rozwijane menu, a kiedy je zamkniesz, anulujesz subskrypcjęELEGANCKA METODA
Znalazłem tę
clickOut
dyrektywę: https://github.com/chliebel/angular2-click-outside . Sprawdzam i działa dobrze (kopiuję tylkoclickOutside.directive.ts
do mojego projektu). Możesz to wykorzystać w ten sposób:Gdzie
close
jest twoja funkcja, która zostanie wywołana, gdy użytkownik kliknie poza div. Jest to bardzo elegancki sposób na rozwiązanie opisywanego problemu.Jeśli używasz powyższej dyrektywy, aby zamknąć okno popUp, pamiętaj, aby najpierw dodać
event.stopPropagation()
do programu obsługi zdarzenia kliknięcia przycisku, który otwiera okno popUp.PREMIA:
Poniżej kopiuję kod dyrektywy z pliku
clickOutside.directive.ts
(na wypadek gdyby link przestał działać w przyszłości) - autorem jest Christian Liebel :Pokaż fragment kodu
źródło
<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Zrobiłem to w ten sposób.
Dodano detektor zdarzeń na dokumencie
click
iw tym module obsługi sprawdzono, czy mojecontainer
zawieraevent.target
, jeśli nie - ukryj listę rozwijaną.Wyglądałoby to tak.
źródło
this.offClickHandler
funkcję strzałkową.Myślę, że zaakceptowana odpowiedź Sasxa działa dla większości ludzi. Miałem jednak sytuację, w której zawartość elementu, który powinien nasłuchiwać zdarzeń off-click, zmieniała się dynamicznie. Zatem element nativeElement nie zawierał zdarzenia event.target, gdy został utworzony dynamicznie. Mógłbym rozwiązać ten problem za pomocą następującej dyrektywy
Zamiast sprawdzać, czy elementRef zawiera event.target, sprawdzam, czy elementRef znajduje się w ścieżce (DOM path to target) zdarzenia. W ten sposób można obsługiwać dynamicznie tworzone elementy.
źródło
Jeśli robisz to na iOS, użyj również
touchstart
wydarzenia:Od Angular 4
HostListener
dekorowanie jest preferowanym sposobem na zrobienie tegoźródło
Pracowaliśmy dzisiaj nad podobnym problemem w pracy, próbując wymyślić, jak sprawić, by rozwijany element div znikał po kliknięciu go. Nasze pytanie różni się nieco od pierwszego pytania zamieszczającego, ponieważ nie chcieliśmy odrywać się od innego składnika lub dyrektywy , a jedynie poza konkretny element div.
Skończyło się na tym, że rozwiązaliśmy to za pomocą procedury obsługi zdarzeń (window: mouseup).
Kroki:
1.) Nadaliśmy całemu div menu rozwijanego unikalną nazwę klasy.
2.) W samym wewnętrznym menu rozwijanym (jedyna część, którą chcieliśmy kliknąć, aby NIE zamknąć menu), dodaliśmy procedurę obsługi zdarzenia (window: mouseup) i przekazaliśmy zdarzenie $.
UWAGA: Nie można tego zrobić za pomocą typowego modułu obsługi kliknięcia, ponieważ powoduje to konflikt z nadrzędnym modułem obsługi kliknięcia.
3.) W naszym kontrolerze stworzyliśmy metodę, którą chcieliśmy wywołać w zdarzeniu click out, i używamy event.closest ( dokumentacja tutaj ), aby dowiedzieć się, czy kliknięte miejsce znajduje się w naszym elemencie div klasy docelowej.
źródło
.closest()
na dzień dzisiejszy nie jest obsługiwane w IE / Edge ( caniuse )Możesz utworzyć element siostrzany w menu rozwijanym, który obejmuje cały ekran, który byłby niewidoczny i dostępny tylko do przechwytywania zdarzeń kliknięcia. Następnie możesz wykryć kliknięcia tego elementu i zamknąć menu po kliknięciu. Powiedzmy, że element jest klasy sitodruku, oto styl:
Indeks Z musi być wystarczająco wysoki, aby umieścić go nad wszystkim oprócz listy rozwijanej. W tym przypadku moje menu rozwijane byłoby b z-index 2.
Inne odpowiedzi działały w niektórych przypadkach dla mnie, z wyjątkiem tego, że czasami moje menu zamykało się, gdy wchodziłem w interakcję z elementami w nim zawartymi, a tego nie chciałem. Dodałem dynamicznie elementy, których nie było w moim komponencie, zgodnie z celem zdarzenia, tak jak się spodziewałem. Zamiast uporządkować ten bałagan, pomyślałem, że po prostu spróbuję tego metodą sitodruku.
źródło
Nie wykonałem żadnego obejścia. Właśnie załączyłem dokument: kliknij moją funkcję przełączania w następujący sposób:
Tak więc, gdy jestem poza zakresem mojej dyrektywy, zamykam menu.
źródło
WADY: - Dwa detektory zdarzeń kliknięcia dla każdego z tych składników na stronie. Nie używaj tego na komponentach, które znajdują się na stronie setki razy.
źródło
Chciałbym uzupełnić odpowiedź @Tony, ponieważ zdarzenie nie jest usuwane po kliknięciu poza komponentem. Kompletny odbiór:
Oznacz swój główny element #container
Na klikalnym elemencie użyj:
Teraz możesz kontrolować stan listy rozwijanej za pomocą zmiennej dropstatus i stosować odpowiednie klasy za pomocą [ngClass] ...
źródło
Możesz napisać dyrektywę:
W twoim komponencie:
Ta dyrektywa emituje zdarzenie, gdy element html jest zawarty w DOM i gdy właściwość input [clickOut] ma wartość „true”. Nasłuchuje zdarzenia mousedown, aby obsłużyć zdarzenie, zanim element zostanie usunięty z DOM.
I jedna uwaga: Firefox nie zawiera właściwości 'path' w przypadku zdarzenia, którego możesz użyć do utworzenia ścieżki:
Więc powinieneś zmienić obsługę zdarzeń w dyrektywie: event.path powinien zostać zastąpiony getEventPath (event)
Ten moduł może pomóc. https://www.npmjs.com/package/ngx-clickout Zawiera tę samą logikę, ale obsługuje również zdarzenie esc w źródłowym elemencie html.
źródło
Prawidłowa odpowiedź ma problem, jeśli masz klikalny komponent w swoim wyskakującym okienku, element nie będzie już w
contain
metodzie i zostanie zamknięty, na podstawie @ JuHarm89 stworzyłem własny:Dzięki za pomoc!
źródło
Lepsza wersja dla @Tony świetne rozwiązanie:
W pliku css: // NIE jest potrzebne, jeśli używasz listy rozwijanej bootstrap.
źródło
Powinieneś sprawdzić, czy zamiast tego klikasz nakładkę modalną, o wiele łatwiej.
Twój szablon:
I metoda:
źródło
Stworzyłem dyrektywę, aby rozwiązać ten podobny problem i używam Bootstrap. Ale w moim przypadku zamiast czekać, aż zdarzenie kliknięcia poza elementem zamknie aktualnie otwarte menu rozwijane, myślę, że lepiej jest obserwować zdarzenie „mouseleave”, aby automatycznie zamknąć menu.
Oto moje rozwiązanie:
Dyrektywa
HTML
źródło
Jeśli używasz Bootstrap, możesz to zrobić bezpośrednio za pomocą metody bootstrap poprzez listy rozwijane (komponent Bootstrap).
Teraz można umieścić
(click)="clickButton()"
rzeczy na przycisku. http://getbootstrap.com/javascript/#dropdownsźródło
Zrobiłem też własne małe obejście.
Utworzyłem zdarzenie (dropdownOpen) , którego słucham w moim komponencie elementu ng-select i wywołuję funkcję, która zamknie wszystkie inne otwarte elementy SelectComponent oprócz aktualnie otwartego SelectComponent.
Zmodyfikowałem jedną funkcję w pliku select.ts , jak poniżej, aby emitować zdarzenie:
W HTML dodałem detektor zdarzeń dla (dropdownOpened) :
To jest moja funkcja wywołująca wyzwalacz zdarzenia wewnątrz komponentu mającego tag ng2-select:
źródło
UWAGA: Dla tych, którzy chcą korzystać z pracowników sieci i powinni unikać korzystania z document i nativeElement, to zadziała.
Odpowiedziałem na to samo pytanie tutaj: /programming/47571144
Skopiuj / wklej z powyższego linku:
Miałem ten sam problem, gdy tworzyłem menu rozwijane i okno dialogowe potwierdzenia, które chciałem odrzucić po kliknięciu na zewnątrz.
Moja ostateczna implementacja działa idealnie, ale wymaga trochę animacji css3 i stylizacji.
UWAGA : nie testowałem poniższego kodu, mogą wystąpić problemy ze składnią, które należy rozwiązać, a także oczywiste poprawki dla własnego projektu!
Co ja zrobiłem:
Zrobiłem osobny stały div z wysokością 100%, szerokością 100% i transformacją: scale (0), to jest w zasadzie tło, możesz je stylizować na kolor tła: rgba (0, 0, 0, 0.466); aby było oczywiste, że menu jest otwarte, a tło jest zamykane przez kliknięcie. Menu otrzymuje indeks Z wyższy niż wszystko inne, a następnie element div tła otrzymuje indeks Z niższy niż menu, ale także wyższy niż wszystko inne. Następnie w tle znajduje się zdarzenie kliknięcia, które zamyka menu.
Tutaj jest z Twoim kodem HTML.
Oto css3, który potrzebuje kilku prostych animacji.
Jeśli nie szukasz niczego wizualnego, możesz po prostu użyć takiego przejścia
źródło
Natrafiłem na inne rozwiązanie, zainspirowane przykładami z fokusem / rozmyciem.
Tak więc, jeśli chcesz osiągnąć tę samą funkcjonalność bez dołączania globalnego nasłuchiwania dokumentów, możesz rozważyć następujący przykład jako prawidłowy. Działa również w Safari i Firefox na OSx, mimo że mają inną obsługę zdarzenia skupienia przycisku: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
Przykład pracy na stackbiz z Angular 8: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts
Znaczniki HTML:
Dyrektywa będzie wyglądać następująco:
źródło
Możesz użyć
mouseleave
w swoim widoku w ten sposóbPrzetestuj za pomocą kątownika 8 i działaj idealnie
źródło
NAJBARDZIEJ ELEGANCKA METODA: D.
Jest na to najłatwiejszy sposób, nie potrzeba do tego żadnych dyrektyw.
„element-to-przełącz-twoje-menu” powinno być tagiem przycisku. Użyj dowolnej metody w atrybucie (blur). To wszystko.
źródło