पता लगाएँ कि कोणीय घटक के बाहर क्लिक करें


जवाबों:


187
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';
  }
}

एक कार्य उदाहरण - यहाँ क्लिक करें


13
जब ट्रिगर तत्व के अंदर एनजीआईएफ द्वारा नियंत्रित कोई तत्व होता है तो यह काम नहीं करता है, क्योंकि एनजीआईआईएफ पर क्लिक करने से पहले एलिमेंट घटने से पहले होता है: plnkr.co/edit/spctsLxkFCxNqLnf5E?p=preview
J. Frankenstein

क्या यह एक ऐसे घटक पर काम करता है जो डायनेमिकली बनाया गया है: const factory = this.resolver.resolveComponentFactory (MyComponent); const elem = this.vcr.createComponent (factory);
एवी मॉरल

1
इस विषय पर एक अच्छा लेख: christianliebel.com/2016/05/…
मिगुएल लारा

47

AMagyar के जवाब के लिए एक विकल्प। जब आप DOM से 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;
  }


यह एनजीएफ या डायनेमिक अपडेट के साथ पूरी तरह से काम करता है
विकास कंडारी


23

दस्तावेज़ को @Hostlistener के माध्यम से क्लिक करना महंगा है। यदि आप अधिक उपयोग करते हैं (उदाहरण के लिए, कस्टम ड्रॉपडाउन घटक का निर्माण करते समय और आपके पास एक रूप में निर्मित कई उदाहरण हैं) तो इसका एक दृश्य प्रदर्शन प्रभाव हो सकता है।

मैं सुझाव देता हूं कि अपने मुख्य ऐप घटक के अंदर केवल एक बार दस्तावेज़ क्लिक इवेंट में @Hostlistener () को जोड़ना। घटना को वैश्विक उपयोगिता सेवा में संग्रहीत सार्वजनिक विषय के अंदर क्लिक किए गए लक्ष्य तत्व के मूल्य को धक्का देना चाहिए।

@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>()
}

जो भी क्लिक किए गए लक्ष्य तत्व के लिए इच्छुक है, उसे हमारी उपयोगिताओं सेवा के सार्वजनिक विषय की सदस्यता लेनी चाहिए और घटक के नष्ट होने पर सदस्यता समाप्त कर देनी चाहिए।

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
  }

4
मुझे लगता है कि यह एक स्वीकृत उत्तर बन जाना चाहिए क्योंकि यह कई अनुकूलन के लिए अनुमति देता है: जैसे इस उदाहरण में
edoardo849

यह सबसे अच्छा समाधान है जो मुझे इंटरनेट पर मिला
अनूप बंगले ने

1
@lampshade सही। मैंने इस बारे में बात की। जवाब फिर से पढ़ें। मैं आपकी शैली के लिए सदस्यता समाप्त कर देता हूं (takeUntil (), Subscription.add ())। सदस्यता समाप्त करने के लिए मत भूलना!
गिन्लेक्स

@ginalx मैंने आपका समाधान लागू किया, यह अपेक्षा के अनुरूप काम करता है। हालाँकि मैं इसे इस्तेमाल करने के तरीके में एक समस्या थी। यहाँ सवाल है, कृपया एक नज़र डालें
नीलेश

6

उपर्युक्त उत्तर सही हैं, लेकिन क्या होगा यदि आप संबंधित घटक से ध्यान हटाने के बाद एक भारी प्रक्रिया कर रहे हैं। उसके लिए, मैं दो झंडे के साथ एक समाधान के साथ आया था, जहां फोकस आउट इवेंट प्रक्रिया केवल प्रासंगिक घटक से फोकस खोने पर होगी।

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;
}

आशा है कि यह आपकी मदद करेगा। मुझे सुधारो अगर कुछ भी याद नहीं है।



2

ginalx के उत्तर को डिफ़ॉल्ट एक imo के रूप में सेट किया जाना चाहिए: यह विधि कई अनुकूलन के लिए अनुमति देती है।

समस्या

यह कहें कि हमारे पास मदों की एक सूची है और प्रत्येक आइटम पर हम एक मेनू को शामिल करना चाहते हैं जिसे टॉगल करने की आवश्यकता है। हम एक बटन पर एक टॉगल शामिल करते हैं जो clickअपने आप में एक घटना के लिए सुनता है (click)="toggle()", लेकिन जब भी उपयोगकर्ता इसके बाहर क्लिक करता है तो हम मेनू को भी चालू करना चाहते हैं। यदि आइटम की सूची बढ़ती है और हम @HostListener('document:click')प्रत्येक मेनू पर संलग्न करते हैं , तो आइटम के भीतर लोड किए गए प्रत्येक मेनू को पूरे दस्तावेज़ पर क्लिक के लिए सुनना शुरू हो जाएगा, तब भी जब मेनू बंद हो जाता है। स्पष्ट प्रदर्शन के मुद्दों के अलावा, यह अनावश्यक है।

उदाहरण के लिए, जब भी पॉपअप एक क्लिक के माध्यम से टॉगल हो जाता है, सदस्यता लें और उसके बाद ही "बाहर के क्लिक" के लिए सुनना शुरू करें।


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();
}

और, इसमें *.component.html:


<button (click)="toggle()">Toggle the menu</button>

जब मैं आपके सोचने के तरीके से सहमत होता हूं, तो मेरा सुझाव है कि आप सभी तर्क को एक tapऑपरेटर में नहीं भरेंगे । इसके बजाय, उपयोग करें skipWhile(() => !this.isActive), switchMap(() => this._utilitiesService.documentClickedTarget), filter((target) => !this._elementRef.nativeElement.contains(target)), tap(() => this._toggleMenuSubject$.next(false))। इस तरह से आप RxJs का अधिक उपयोग करते हैं और कुछ सदस्यताएँ छोड़ देते हैं।
गिज्रह


-1

यू इवेंट फंक्शन को कॉल कर सकते हैं जैसे (फ़ोकसआउट) या (ब्लर) तो यू अपना कोड डालें

<div tabindex=0 (blur)="outsideClick()">raw data </div>
 

 outsideClick() {
  alert('put your condition here');
   }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.