From e39b22a932384f228e97414c44d10c7e158dfd2f Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Tue, 6 Aug 2024 14:45:05 -0700 Subject: [PATCH] fix(core): Account for addEventListener to be passed a Window or Document. (#57282) This happened to work for other event listeners since both had a addEventListener method. PR Close #57282 --- packages/core/src/event_delegation_utils.ts | 22 ++++++++---- .../event_dispatch/event_dispatch_spec.ts | 34 ++++++++++++++++++- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/core/src/event_delegation_utils.ts b/packages/core/src/event_delegation_utils.ts index 51a788cfc1b..fcaae34addd 100644 --- a/packages/core/src/event_delegation_utils.ts +++ b/packages/core/src/event_delegation_utils.ts @@ -93,15 +93,25 @@ export class GlobalEventDelegation implements OnDestroy { return isEarlyEventType(eventType); } - addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { - this.eventContractDetails.instance!.addEvent(eventName); - sharedStashFunction(element, eventName, handler); - getActionCache(element)[eventName] = ''; - return () => this.removeEventListener(element, eventName, handler); + addEventListener(element: HTMLElement, eventType: string, handler: Function): Function { + // Note: contrary to the type, Window and Document can be passed in + // as well. + if (element.nodeType === Node.ELEMENT_NODE) { + this.eventContractDetails.instance!.addEvent(eventType); + getActionCache(element)[eventType] = ''; + sharedStashFunction(element, eventType, handler); + } else { + element.addEventListener(eventType, handler as EventListener); + } + return () => this.removeEventListener(element, eventType, handler); } removeEventListener(element: HTMLElement, eventType: string, callback: Function): void { - getActionCache(element)[eventType] = undefined; + if (element.nodeType === Node.ELEMENT_NODE) { + getActionCache(element)[eventType] = undefined; + } else { + element.removeEventListener(eventType, callback as EventListener); + } } } diff --git a/packages/core/test/event_dispatch/event_dispatch_spec.ts b/packages/core/test/event_dispatch/event_dispatch_spec.ts index 81687241d8f..df701a71175 100644 --- a/packages/core/test/event_dispatch/event_dispatch_spec.ts +++ b/packages/core/test/event_dispatch/event_dispatch_spec.ts @@ -6,7 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Renderer2, inject, ɵprovideGlobalEventDelegation} from '@angular/core'; +import { + Component, + HostListener, + Renderer2, + inject, + ɵprovideGlobalEventDelegation, +} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; function configureTestingModule(components: unknown[]) { @@ -216,5 +222,31 @@ describe('event dispatch', () => { bottomEl.click(); expect(onClickSpy).toHaveBeenCalledTimes(2); }); + it('should allow host listening on the window', async () => { + const onClickSpy = jasmine.createSpy(); + @Component({ + standalone: true, + selector: 'app', + template: ` +
+
+
+ `, + }) + class SimpleComponent { + renderer = inject(Renderer2); + destroy!: Function; + @HostListener('window:click', ['$event.target']) + listen(el: Element) { + onClickSpy(); + } + } + configureTestingModule([SimpleComponent]); + fixture = TestBed.createComponent(SimpleComponent); + const nativeElement = fixture.debugElement.nativeElement; + const bottomEl = nativeElement.querySelector('#bottom')!; + bottomEl.click(); + expect(onClickSpy).toHaveBeenCalledTimes(1); + }); }); });