เพิ่มตัวฟังเหตุการณ์แบบไดนามิก


143

ฉันเพิ่งเริ่มยุ่งกับ Angular 2 และฉันสงสัยว่าใครสามารถบอกวิธีที่ดีที่สุดในการเพิ่มและลบผู้ฟังเหตุการณ์จากองค์ประกอบ

ฉันมีส่วนประกอบตั้งค่าแล้ว เมื่อมีการคลิกองค์ประกอบบางอย่างในแม่แบบฉันต้องการเพิ่มผู้ฟังสำหรับmousemoveองค์ประกอบอื่นของแม่แบบเดียวกัน ฉันต้องการลบผู้ฟังนี้เมื่อมีการคลิกองค์ประกอบที่สาม

ฉันได้งานนี้แค่ใช้ Javascript ธรรมดาเพื่อจับองค์ประกอบแล้วเรียกมาตรฐานaddEventListener()แต่ฉันสงสัยว่ามี " Angular2.0 " วิธีการทำเช่นนี้มากกว่าที่ฉันควรจะดู

คำตอบ:


262

Renderer เลิกใช้แล้วใน Angular 4.0.0-rc.1 อ่านการอัปเดตด้านล่าง

วิธี angular2คือการใช้งานlistenหรือlistenGlobalจากการแสดงผล

ตัวอย่างเช่นหากคุณต้องการเพิ่มเหตุการณ์การคลิกไปยังคอมโพเนนต์คุณต้องใช้ Renderer และ ElementRef (สิ่งนี้จะช่วยให้คุณมีตัวเลือกในการใช้ ViewChild หรือสิ่งใด ๆ ที่ดึงข้อมูล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'
});

โปรดทราบว่าตั้งแต่ beta.2 ทั้งคู่listenและlistenGlobalส่งคืนฟังก์ชันเพื่อลบผู้ฟัง (ดูที่การแบ่งการเปลี่ยนแปลงจาก changelog สำหรับ beta.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พร้อมตัวอย่างที่ใช้งานได้ ตัวอย่างที่มีการใช้งานของและlistenlistenGlobal

การใช้ RendererV2 กับ Angular 4.0.0-rc.1 + (Renderer2 ตั้งแต่ 4.0.0-rc.3)

  • 25/02/2017 : Rendererเลิกใช้แล้วตอนนี้เราควรใช้RendererV2(ดูบรรทัดด้านล่าง) ดูการกระทำ

  • 2017/10/03 : เปลี่ยนชื่อเป็นRendererV2 Renderer2ดูการเปลี่ยนแปลงที่จะหมด

RendererV2ไม่มีlistenGlobalฟังก์ชั่นเพิ่มเติมสำหรับกิจกรรมทั่วโลก (เอกสาร, เนื้อหา, หน้าต่าง) มันมีlistenฟังก์ชั่นที่ใช้งานได้ทั้งสองฟังก์ชั่นเท่านั้น

สำหรับการอ้างอิงฉันคัดลอก & วางรหัสแหล่งที่มาของการใช้งาน DOM Renderer เนื่องจากอาจเปลี่ยนแปลงได้ (ใช่มันเป็นเชิงมุม!)

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ฟังก์ชั่นภายใน ในกรณีอื่น ๆ เมื่อเราผ่านองค์ประกอบ (nativeElement) มันจะใช้ง่าย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พร้อมAngular 4.0.0-rc.1โดยใช้RendererV2

plnkrพร้อมAngular 4.0.0-rc.3โดยใช้Renderer2


นี่เป็นเพียงวันที่สองของฉันกับ Angular2 และฉันเพิ่งเริ่มได้รับหัวของฉันประมาณ v1 ดังนั้นจำนวนมากนี้ค่อนข้างสับสนใหม่ คุณให้สิ่งดีๆมากมายแก่ฉันที่จะอ่านต่อดังนั้นฉันจะปิดมันลงและจะไม่ต้องสงสัยกลับมาอีกในเร็ว ๆ นี้พร้อมกับคำถามมากมายที่เกี่ยวข้องกับ LOTS ไชโยสำหรับการตอบสนองรายละเอียด :)
popClingwrap

3
@ popClingwrap คุณสามารถตรวจสอบHostListener ได้เช่นกัน ในเอกสารให้ตรวจสอบคำสั่งแอตทริบิวต์ภายใต้การตอบสนองต่อการกระทำของผู้ใช้เพื่อดูว่าhostมีการใช้งานอย่างไร
Eric Martinez

@EricMartinez มีวิธีหยุดฟังทั้งฟังหรือฟัง Global หรือไม่ (เหมือนกับ removeEventListener)
Nik

3
@ user1394625 ใช่ดังที่คุณเห็นในคำตอบngOnDestroyโค้ดทั้งคู่listenและlistenGlobalส่งคืนฟังก์ชันที่เมื่อถูกเรียก / ดำเนินการฟังจะถูกลบออก อย่างที่คุณเห็นthis.funcคือการถือฟังก์ชั่นที่ส่งคืนโดยrenderer.listenและเมื่อฉันthis.func()ฉันจะลบผู้ฟัง listenGlobalเดียวกันจะไปสำหรับ
Eric Martinez

@EricMartinez มีคำถามอีกหนึ่งคำถามสำหรับคุณ ... ฉันจะเข้าถึง 'เหตุการณ์' ในฟังก์ชันเพื่อป้องกันการผิดเริ่มต้น () หรือ stopPropagation () ได้อย่างไร
Nik

5

ฉันยังพบว่านี่ทำให้เกิดความสับสนอย่างมาก เป็น @EricMartinez ให้คะแนน Renderer2 Listen () ส่งคืนฟังก์ชันเพื่อลบ listener:

ƒ () { 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
หากคุณเพิ่มผู้ฟังทำไมคุณถึงคาดหวังว่าฟังก์ชั่นที่ส่งคืนโดยการเพิ่มผู้ฟังนั้นจะเรียกผู้ฟังนั้น? นั่นไม่สมเหตุสมผลเลยสำหรับฉัน จุดรวมของการเพิ่มฟังคือการตอบสนองต่อเหตุการณ์ที่คุณไม่สามารถเรียกโปรแกรม ฉันคิดว่าถ้าคุณคาดว่าฟังก์ชั่นนี้จะเรียกผู้ฟังคุณอาจไม่เข้าใจผู้ฟังอย่างสมบูรณ์
Willwsharp

@tahiche คู่นี้มันสับสนจริงๆขอบคุณสำหรับการชี้ให้เห็น!
godblessstrawberry

มันส่งคืนสิ่งนี้เพื่อให้คุณสามารถเอาผู้ฟังออกได้อีกเมื่อคุณทำลายส่วนประกอบของคุณในภายหลัง เมื่อเพิ่มผู้ฟังจะเป็นการดีที่จะลบออกภายหลังเมื่อคุณไม่ต้องการใช้อีกต่อไป ดังนั้นเก็บค่าส่งคืนและเรียกมันในngOnDestroyวิธีการของคุณ ฉันยอมรับว่ามันอาจดูสับสนในตอนแรก แต่จริงๆแล้วมันเป็นคุณสมบัติที่มีประโยชน์มาก ทำความสะอาดหลังจากตัวเองเป็นอย่างไร
ร่วงโรย

1

ฉันจะเพิ่มตัวอย่าง StackBlitzและความคิดเห็นคำตอบจาก @tahiche

ค่าที่ส่งคืนเป็นฟังก์ชันในการลบ listener เหตุการณ์หลังจากที่คุณเพิ่มเข้าไป ถือว่าเป็นการปฏิบัติที่ดีในการลบผู้ฟังเหตุการณ์ออกเมื่อคุณไม่ต้องการอีกต่อไป ดังนั้นคุณสามารถเก็บค่าส่งคืนนี้และเรียกมันใน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

นี่เป็นวิธีแก้ปัญหาของฉัน:

ฉันสร้างห้องสมุดด้วย Angular 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>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.