From cc929727e26ccae787171d0db62048669ceec5ca Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 21 Feb 2017 00:50:22 -0800 Subject: [PATCH] fix(HostListener): invoke listeners within a digest Fixes #11 --- src/facade.spec.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++++ src/facade.ts | 16 +++++++++----- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/facade.spec.ts b/src/facade.spec.ts index e41e0f1..249b7ca 100644 --- a/src/facade.spec.ts +++ b/src/facade.spec.ts @@ -1277,6 +1277,61 @@ describe("facade", function() { expect(bar).toHaveBeenCalled(); }); + it("should invoke the expression within a digest", function() { + const foo = jasmine.createSpy("foo event callback"); + + let phase; + @Component({ + selector: "comp" + }) + class Comp { + constructor(@Inject("$rootScope") public $rootScope) {} + + @HostListener("asdf") + adsf() { + phase = this.$rootScope.$$phase; + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(phase).toBeUndefined(); + $dom.triggerHandler("asdf"); + expect(phase).toBe("$apply"); + }); + + it("should support DOM events triggered while already in a digest", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + constructor(@Inject("$element") private $element) {} + @HostListener("asdf") + adsf() { + foo.apply(this, arguments); + } + + @HostListener("first") + first() { + this.$element.triggerHandler("asdf"); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("first"); + expect(foo).toHaveBeenCalled(); + }); + it("should pass arguments specified in @HostListener('asdf', [...args])", function() { const foo = jasmine.createSpy("foo event callback"); diff --git a/src/facade.ts b/src/facade.ts index 5709b88..a99b02f 100644 --- a/src/facade.ts +++ b/src/facade.ts @@ -434,18 +434,24 @@ export class EventEmitter { //https://github.com/angular/angular/blob/2.4.5/modules/%40angular/core/src/metadata/directives.ts#L1005-L1017 export function HostListener(eventType: string, args: string[] = []): MethodDecorator { return function(targetPrototype: Object, propertyKey: string): void { - function HostListenerSetup($element: JQuery, $parse: angular.IParseService, $injector: angular.auto.IInjectorService): void { + function HostListenerSetup($element: JQuery, $parse: angular.IParseService, $rootScope: angular.IScope): void { //Parse the listener arguments on component initialization const argExps = args.map((s) => $parse(s)); - $element.on(eventType, ($event) => { - //Invoke each argument expression using only the $event local + $element.on(eventType, ($event: BaseJQueryEventObject) => { + //Invoke each argument expression specifying the $event local const argValues = argExps.map((argExp) => argExp({$event})); + const invokeListener = () => this[propertyKey](...argValues); - this[propertyKey](...argValues); + if (!$rootScope.$$phase) { + $rootScope.$apply(invokeListener); + } + else { + invokeListener(); + } }); } - HostListenerSetup.$inject = ["$element", "$parse", "$injector"]; + HostListenerSetup.$inject = ["$element", "$parse", "$rootScope"]; addPreLink(targetPrototype, HostListenerSetup); };