diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts index 735846d505..918a121de7 100644 --- a/packages/platform-browser/src/dom/events/dom_events.ts +++ b/packages/platform-browser/src/dom/events/dom_events.ts @@ -203,16 +203,26 @@ export class DomEventsPlugin extends EventManagerPlugin { // just call native removeEventListener return target[NATIVE_REMOVE_LISTENER].apply(target, [eventName, callback, false]); } + // fix issue 20532, should be able to remove + // listener which was added inside of ngZone + let found = false; for (let i = 0; i < taskDatas.length; i++) { // remove listener from taskDatas if the callback equals if (taskDatas[i].handler === callback) { + found = true; taskDatas.splice(i, 1); break; } } - if (taskDatas.length === 0) { - // all listeners are removed, we can remove the globalListener from target - underlyingRemove.apply(target, [eventName, globalListener, false]); + if (found) { + if (taskDatas.length === 0) { + // all listeners are removed, we can remove the globalListener from target + underlyingRemove.apply(target, [eventName, globalListener, false]); + } + } else { + // not found in taskDatas, the callback may be added inside of ngZone + // use native remove listener to remove the calback + target[NATIVE_REMOVE_LISTENER].apply(target, [eventName, callback, false]); } } } diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts index e1a405e9b9..ea4c35c984 100644 --- a/packages/platform-browser/test/dom/events/event_manager_spec.ts +++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts @@ -255,6 +255,46 @@ export function main() { expect(receivedEvents).toEqual([]); }); + it('should be able to remove event listener which was added inside of ngZone', () => { + const Zone = (window as any)['Zone']; + + const element = el('
'); + getDOM().appendChild(doc.body, element); + const dispatchedEvent = getDOM().createMouseEvent('click'); + let receivedEvents: any[] /** TODO #9100 */ = []; + let receivedZones: any[] = []; + const handler1 = (e: any /** TODO #9100 */) => { + receivedEvents.push(e); + receivedZones.push(Zone.current.name); + }; + const handler2 = (e: any /** TODO #9100 */) => { + receivedEvents.push(e); + receivedZones.push(Zone.current.name); + }; + const manager = new EventManager([domEventPlugin], new FakeNgZone()); + + let remover1 = null; + let remover2 = null; + // handler1 is added in root zone + Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); }); + // handler2 is added in 'angular' zone + Zone.root.fork({name: 'fakeAngularZone', properties: {isAngularZone: true}}).run(() => { + remover2 = manager.addEventListener(element, 'click', handler2); + }); + getDOM().dispatchEvent(element, dispatchedEvent); + expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]); + expect(receivedZones).toEqual([Zone.root.name, 'fakeAngularZone']); + + receivedEvents = []; + remover1 && remover1(); + remover2 && remover2(); + getDOM().dispatchEvent(element, dispatchedEvent); + // handler1 and handler2 are added in different zone + // one is angular zone, the other is not + // should still be able to remove them correctly + expect(receivedEvents).toEqual([]); + }); + it('should run blackListedEvents handler outside of ngZone', () => { const Zone = (window as any)['Zone']; const element = el('
');