डायनामिक रूप से ईवेंट श्रोता जोड़ें


143

मैं सिर्फ कोणीय 2 के साथ गड़बड़ करना शुरू कर रहा हूं और मुझे आश्चर्य है कि अगर कोई मुझे तत्वों के साथ घटना श्रोताओं को गतिशील रूप से जोड़ने और हटाने का सबसे अच्छा तरीका बता सकता है।

मेरे पास एक घटक है। जब टेम्पलेट में एक निश्चित तत्व पर क्लिक किया जाता है तो मैं mousemoveउसी टेम्पलेट के दूसरे तत्व के लिए श्रोता जोड़ना चाहता हूं । मैं तब इस श्रोता को निकालना चाहता हूं जब कोई तीसरा तत्व क्लिक किया जाता है।

मुझे इस तरह का काम मिला सिर्फ सादे जावास्क्रिप्ट का उपयोग करके तत्वों को हथियाने के लिए और फिर मानक को बुलाने के लिए addEventListener()लेकिन मैंने सोचा कि क्या ऐसा करने का एक और " Angular2.0 " तरीका है जिसे मुझे देखना चाहिए।

जवाबों:


262

रेंडरर को एंगुलर 4.0.0-rc.1 में अपग्रेड किया गया है, नीचे दिए गए अपडेट को पढ़ें

Angular2 तरह से उपयोग करने के लिए है listenया listenGlobalसे प्रतिपादक

उदाहरण के लिए, यदि आप किसी कंपोनेंट में क्लिक ईवेंट जोड़ना चाहते हैं, तो आपको रेंडरर और एलिमेंट रीफ का उपयोग करना होगा (यह आपको व्यूचाइल्ड का उपयोग करने का विकल्प देता है, या जो कुछ भी प्राप्त होता है nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

    // Listen to click events in the component
    renderer.listen(elementRef.nativeElement, 'click', (event) => {
      // Do something with 'event'
    })
);

आप उपयोग कर सकते हैं listenGlobalकि आप के लिए पहुँच दे देंगे document, bodyआदि

renderer.listenGlobal('document', 'click', (event) => {
  // Do something with 'event'
});

ध्यान दें कि बीटा.2 के बाद से दोनों listenऔर listenGlobalश्रोता को हटाने के लिए एक फ़ंक्शन लौटाते हैं ( बीटा 2 के लिए चैंज से परिवर्तन अनुभाग देखें )। यह बड़े अनुप्रयोगों में मेमोरी लीक से बचने के लिए है ( # 6686 देखें )।

इसलिए हमने जिस श्रोता को गतिशील रूप से जोड़ा है उसे निकालने के लिए हमें असाइन करना चाहिए listenया फिर listenGlobalएक चर है जो फ़ंक्शन को वापस लौटाएगा, और फिर हम इसे निष्पादित करते हैं।

// listenFunc will hold the function returned by "renderer.listen"
listenFunc: Function;

// globalListenFunc will hold the function returned by "renderer.listenGlobal"
globalListenFunc: Function;

constructor(elementRef: ElementRef, renderer: Renderer) {
    
    // We cache the function "listen" returns
    this.listenFunc = renderer.listen(elementRef.nativeElement, 'click', (event) => {
        // Do something with 'event'
    });

    // We cache the function "listenGlobal" returns
    this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
        // Do something with 'event'
    });
}

ngOnDestroy() {
    // We execute both functions to remove the respectives listeners

    // Removes "listen" listener
    this.listenFunc();
    
    // Removs "listenGlobal" listener
    this.globalListenFunc();
}

यहाँ एक है plnkr एक उदाहरण काम कर रहे हैं। उदाहरण के उपयोग में शामिल है listenऔर listenGlobal

Angular 4.0.0-rc.1 + (Renderer2 4.0 4.0-rc.3 के साथ) RendererV2 का उपयोग करना

  • 25/02/2017 : Rendererपदावनत कर दिया गया है, अब हमें इसका उपयोग करना चाहिए RendererV2(नीचे पंक्ति देखें)। कमिटमेंट देखें ।

  • 10/03/2017 : RendererV2का नाम बदल दिया गया Renderer2ब्रेकिंग परिवर्तन देखें ।

RendererV2listenGlobalवैश्विक घटनाओं (दस्तावेज़, निकाय, खिड़की) के लिए कोई अधिक कार्य नहीं है । इसमें केवल एक listenफ़ंक्शन होता है जो दोनों कार्यात्मकताओं को प्राप्त करता है।

संदर्भ के लिए, मैं DOM रेंडरर कार्यान्वयन के स्रोत कोड को कॉपी और पेस्ट कर रहा हूं क्योंकि यह बदल सकता है (हां, यह कोणीय है!)।

listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
      () => void {
    if (typeof target === 'string') {
      return <() => void>this.eventManager.addGlobalEventListener(
          target, event, decoratePreventDefault(callback));
    }
    return <() => void>this.eventManager.addEventListener(
               target, event, decoratePreventDefault(callback)) as() => void;
  }

जैसा कि आप देख सकते हैं, अब यह सत्यापित करता है कि यदि हम एक स्ट्रिंग (दस्तावेज़, निकाय या विंडो) पास कर रहे हैं, तो किस स्थिति में यह एक आंतरिक addGlobalEventListenerफ़ंक्शन का उपयोग करेगा । किसी अन्य मामले में, जब हम एक तत्व (नेटिवमेंट) पास करते हैं तो यह एक सरल का उपयोग करेगाaddEventListener

श्रोता को हटाने के लिए यह वैसा ही है जैसा कि Rendererकोणीय 2.x में था । listenएक फ़ंक्शन देता है, फिर उस फ़ंक्शन को कॉल करें।

उदाहरण

// Add listeners
let global = this.renderer.listen('document', 'click', (evt) => {
  console.log('Clicking the document', evt);
})

let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
  console.log('Clicking the button', evt);
});

// Remove listeners
global();
simple();

plnkr साथ कोणीय 4.0.0-rc.1 का उपयोग कर RendererV2

plnkr साथ कोणीय 4.0.0-rc.3 का उपयोग कर Renderer2


यह केवल Angular2 के साथ मेरा दूसरा दिन है और मैंने मुश्किल से v1 के आसपास अपना सिर प्राप्त करना शुरू कर दिया है, इसलिए इसमें से बहुत कुछ नया भ्रमित करना है। आपने मुझे पढ़ने के लिए सामान का एक अच्छा भार दिया है, हालांकि मैं इसे एक बंद कर रहा हूं और बहुत अधिक संबंधित प्रश्नों के साथ जल्द ही वापस आ जाएगा। विस्तृत प्रतिक्रिया के लिए चीयर्स :)
popClingwrap

3
@popClingwrap आप HostListener को भी देख सकते हैं । डॉक्स में, उपयोगकर्ता कार्रवाई के जवाब के तहत गुण निर्देशों की जांच करें कि hostयह कैसे उपयोग किया जाता है।
एरिक मार्टिनेज

@EricMartinez सुनने या सुनने के लिए या तो सुनने को रोकने का एक तरीका है? (वही रिमूवलस्टीनर)
निक

3
@ user1394625 हाँ, जैसा कि आप ngOnDestroyकोड के उत्तर में देख सकते हैं , दोनों listenऔर listenGlobalएक फ़ंक्शन लौटाते हैं जिसे जब बुलाया / निष्पादित किया जाता है तो श्रोता हटा दिया जाता है। तो जैसा कि आप देख रहे हैं this.funcकि इस फंक्शन को होल्ड करके रखा गया है renderer.listenऔर जब मैं कर this.func()रहा हूँ तो मैं श्रोता को हटा रहा हूँ। उसी के लिए जाता है listenGlobal
एरिक मार्टिनेज

@EricMartinez को आपके लिए एक और प्रश्न मिला ... मैं कैसे कर सकता हूं '' ईवेंट '' को 'फंक्शन' के अंदर 'डीफाल्ट' () या स्टॉपप्रॉपैजिनेशन ()
Nik

5

मुझे यह बेहद भ्रामक लगता है। @EricMartinez के अनुसार Renderer2 सुनता है () श्रोता को निकालने के लिए फ़ंक्शन देता है:

ƒ () { return element.removeEventListener(eventName, /** @type {?} */ (handler), false); }

अगर मैं एक श्रोता को जोड़ रहा हूँ

this.listenToClick = this.renderer.listen('document', 'click', (evt) => {
    alert('Clicking the document');
})

मुझे उम्मीद है कि मेरे कार्य को निष्पादित करने का इरादा है, न कि कुल विपरीत जो श्रोता को हटा देता है।

// I´d expect an alert('Clicking the document'); 
this.listenToClick();
// what you actually get is removing the listener, so nothing...

दिए गए परिदृश्य में, वास्तव में इसे नाम देने के लिए और अधिक समझ में आता है:

// Add listeners
let unlistenGlobal = this.renderer.listen('document', 'click', (evt) => {
    console.log('Clicking the document', evt);
})

let removeSimple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
    console.log('Clicking the button', evt);
});

इसका एक अच्छा कारण होना चाहिए लेकिन मेरी राय में यह बहुत भ्रामक है और सहज नहीं है।


3
यदि आप एक श्रोता को जोड़ रहे थे, तो आप क्यों उम्मीद करेंगे कि उस श्रोता को जोड़कर लौटा गया फ़ंक्शन उस श्रोता को आमंत्रित करेगा? इससे मुझे कोई मतलब नहीं है। एक श्रोता को जोड़ने का पूरा बिंदु उन घटनाओं पर प्रतिक्रिया करना है, जिन्हें आप प्रोग्रामेटिक रूप से ट्रिगर नहीं कर सकते। मुझे लगता है कि अगर आपको अपने श्रोता को आमंत्रित करने के लिए उस समारोह की उम्मीद थी, तो आप श्रोताओं को पूरी तरह से नहीं समझ रहे होंगे।
विल्वशर्प

@ यह वास्तव में भ्रामक है, यह इंगित करने के लिए धन्यवाद!
गॉडबलेसस्ट्रॉबेरी

जब आप अपने घटक को बाद में नष्ट कर देते हैं तो यह आपको फिर से श्रोता को हटा सकता है। श्रोताओं को जोड़ते समय उन्हें बाद में हटाने के लिए अच्छा अभ्यास माना जाता है जब आपको उनकी आवश्यकता नहीं होती है। इसलिए इस रिटर्न वैल्यू को स्टोर करें और इसे अपनी ngOnDestroyविधि के अंदर कॉल करें । मैं मानता हूँ कि यह पहली बार में भ्रामक लग सकता है, लेकिन यह वास्तव में एक बहुत ही उपयोगी विशेषता है। खुद के बाद और कैसे साफ करें?
विल्ट

1

मैं एक StackBlitz उदाहरण और @tahiche से जवाब के लिए एक टिप्पणी जोड़ देंगे।

आपके द्वारा जोड़े जाने के बाद ईवेंट श्रोता को निकालने के लिए रिटर्न वैल्यू एक फ़ंक्शन है। इवेंट श्रोताओं को निकालने के लिए अच्छा अभ्यास माना जाता है जब आपको उनकी आवश्यकता नहीं होती है। तो आप इस रिटर्न वैल्यू को स्टोर कर सकते हैं और इसे अपनी ngOnDestroyविधि के अंदर कॉल कर सकते हैं ।

मैं मानता हूँ कि यह पहली बार में भ्रामक लग सकता है, लेकिन यह वास्तव में एक बहुत ही उपयोगी विशेषता है। आप अपने आप को और कैसे साफ कर सकते हैं?

export class MyComponent implements OnInit, OnDestroy {

  public removeEventListener: () => void;

  constructor(
    private renderer: Renderer2, 
    private elementRef: ElementRef
  ) {
  }

  public ngOnInit() {
    this.removeEventListener = this.renderer.listen(this.elementRef.nativeElement, 'click', (event) => {
      if (event.target instanceof HTMLAnchorElement) {
        // Prevent opening anchors the default way
        event.preventDefault();
        // Your custom anchor click event handler
        this.handleAnchorClick(event);
      }
    });
  }

  public ngOnDestroy() {
    this.removeEventListener();
  }
}

एंकर तत्वों पर क्लिक करने के लिए यह कैसे काम कर सकता है, यह दिखाने के लिए आप यहां एक StackBlitz पा सकते हैं ।

मैंने निम्नानुसार एक छवि के साथ एक निकाय जोड़ा:
<img src="x" onerror="alert(1)"></div>
यह दिखाने के लिए कि सैनिटाइज़र अपना काम कर रहा है।

यहाँ इस फिडेल में आपको वही शरीर मिलता है जो innerHTMLइसे बिना स्वच्छता के जुड़ा हुआ है और यह इस मुद्दे को प्रदर्शित करेगा।


0

यहाँ मेरा काम है:

मैंने एंगुलर 6 के साथ एक लाइब्रेरी बनाई। मैंने एक कॉमन कंपोनेंट जोड़ा, commonlib-headerजो एक बाहरी एप्लिकेशन में इस तरह इस्तेमाल किया जाता है।

ध्यान दें कि serviceReferenceवह वर्ग है (घटक constructor(public serviceReference: MyService)का उपयोग करने वाला इंजेक्शन commonlib-header) जो stringFunctionNameविधि रखता है :

<commonlib-header
    [logo]="{ src: 'assets/img/logo.svg', alt: 'Logo', href: '#' }"
    [buttons]="[{ index: 0, innerHtml: 'Button', class: 'btn btn-primary', onClick: [serviceReference, 'stringFunctionName', ['arg1','arg2','arg3']] }]">
    </common-header>

पुस्तकालय घटक इस तरह से क्रमादेशित है। डायनामिक ईवेंट onClick(fn: any)विधि में जोड़ा गया है :

export class HeaderComponent implements OnInit {

 _buttons: Array<NavItem> = []

 @Input()
  set buttons(buttons: Array<any>) {
    buttons.forEach(navItem => {
      let _navItem = new NavItem(navItem.href, navItem.innerHtml)

      _navItem.class = navItem.class

      _navItem.onClick = navItem.onClick // this is the array from the component @Input properties above

      this._buttons[navItem.index] = _navItem
    })
  }

  constructor() {}

  ngOnInit() {}

  onClick(fn: any){
    let ref = fn[0]
    let fnName = fn[1]
    let args = fn[2]

    ref[fnName].apply(ref, args)
  }

पुन header.component.html: प्रयोज्य :

<div class="topbar-right">
  <button *ngFor="let btn of _buttons"
    class="{{ btn.class }}"
    (click)="onClick(btn.onClick)"
    [innerHTML]="btn.innerHtml | keepHtml"></button>
</div>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.