diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts
index fb32c2b455..ae31a5ffe8 100644
--- a/packages/core/src/render3/instructions/listener.ts
+++ b/packages/core/src/render3/instructions/listener.ts
@@ -131,8 +131,11 @@ function listenerInternal(
let processOutputs = true;
- // add native event listener - applicable to elements only
- if (tNode.type & TNodeType.AnyRNode) {
+ // Adding a native event listener is applicable when:
+ // - The corresponding TNode represents a DOM element.
+ // - The event target has a resolver (usually resulting in a global object,
+ // such as `window` or `document`).
+ if ((tNode.type & TNodeType.AnyRNode) || eventTargetResolver) {
const native = getNativeByTNode(tNode, lView) as RElement;
const target = eventTargetResolver ? eventTargetResolver(native) : native;
const lCleanupIndex = lCleanup.length;
diff --git a/packages/core/test/acceptance/listener_spec.ts b/packages/core/test/acceptance/listener_spec.ts
index 671a8baf9b..51bf82f4ff 100644
--- a/packages/core/test/acceptance/listener_spec.ts
+++ b/packages/core/test/acceptance/listener_spec.ts
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Directive, ErrorHandler, EventEmitter, HostListener, Input, Output, QueryList, ViewChild, ViewChildren} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Component, Directive, ErrorHandler, EventEmitter, HostListener, Input, OnInit, Output, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {onlyInIvy} from '@angular/private/testing';
@@ -397,6 +398,108 @@ describe('event listeners', () => {
expect(comp.counter).toBe(1);
});
+ onlyInIvy('global event listeners on non-node host elements are supported only in Ivy')
+ .it('should bind global event listeners on an ng-container directive host', () => {
+ let clicks = 0;
+
+ @Directive({selector: '[add-global-listener]'})
+ class AddGlobalListener {
+ @HostListener('document:click')
+ handleClick() {
+ clicks++;
+ }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ class MyComp {
+ }
+
+ TestBed.configureTestingModule({declarations: [MyComp, AddGlobalListener]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+ const button = fixture.nativeElement.querySelector('button');
+ button.click();
+ fixture.detectChanges();
+ expect(clicks).toBe(1);
+ });
+
+ onlyInIvy('global event listeners on non-node host elements are supported only in Ivy')
+ .it('should bind global event listeners on an ng-template directive host', () => {
+ let clicks = 0;
+
+ @Directive({selector: '[add-global-listener]'})
+ class AddGlobalListener {
+ @HostListener('document:click')
+ handleClick() {
+ clicks++;
+ }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+
+ `
+ })
+ class MyComp {
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [MyComp, AddGlobalListener], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+ const button = fixture.nativeElement.querySelector('button');
+ button.click();
+ fixture.detectChanges();
+ expect(clicks).toBe(1);
+ });
+
+ onlyInIvy('global event listeners on non-node host elements are supported only in Ivy')
+ .it('should bind global event listeners on a structural directive host', () => {
+ let clicks = 0;
+
+ @Directive({selector: '[add-global-listener]'})
+ class AddGlobalListener implements OnInit {
+ @HostListener('document:click')
+ handleClick() {
+ clicks++;
+ }
+
+ constructor(private _vcr: ViewContainerRef, private _templateRef: TemplateRef) {}
+
+ ngOnInit() {
+ this._vcr.createEmbeddedView(this._templateRef);
+ }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ class MyComp {
+ }
+
+ TestBed.configureTestingModule({declarations: [MyComp, AddGlobalListener]});
+ const fixture = TestBed.createComponent(MyComp);
+ fixture.detectChanges();
+ const button = fixture.nativeElement.querySelector('button');
+ button.click();
+ fixture.detectChanges();
+ expect(clicks).toBe(1);
+ });
+
onlyInIvy('issue has only been resolved for Ivy')
.it('should be able to access a property called $event using `this`', () => {
let eventVariable: number|undefined;