From 84e554b5e349123285810cc207f9640ee9a97a7a Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Sat, 18 Feb 2017 17:30:32 -0800 Subject: [PATCH] feat(HostListener): add component HostListener support Closes #6 --- src/facade.spec.ts | 149 ++++++++++++++++++++++++++++++++++++++++++++- src/facade.ts | 29 ++++++++- 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/src/facade.spec.ts b/src/facade.spec.ts index b5f7909..d36126f 100644 --- a/src/facade.spec.ts +++ b/src/facade.spec.ts @@ -2,7 +2,7 @@ import "jasmine"; import "tslib"; import * as angular from "angular"; -import {Inject, Injectable, PipeTransform, Pipe, Input, InputString, InputCallback, Output, EventEmitter, Require, Directive, Component, NgModule} from "./facade"; +import {Inject, Injectable, PipeTransform, Pipe, Input, InputString, InputCallback, Output, EventEmitter, Require, Directive, Component, HostListener, NgModule} from "./facade"; //Copied from facade.ts to avoid exposing publicly const OUTPUT_BOUND_CALLBACK_PREFIX = "__event_"; @@ -992,6 +992,153 @@ describe("facade", function() { })); }); }); + + describe("@HostListener", function() { + it("should bind @HostListener('asdf') to the DOM 'asdf' event", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf") + adsf() { + foo.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalled(); + }); + + it("should bind multiple @HostListener('asdf')s to the DOM 'asdf' event", function() { + const foo = jasmine.createSpy("foo event callback"); + const bar = jasmine.createSpy("bar event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf") + foo() { + foo.apply(this, arguments); + } + @HostListener("asdf") + bar() { + bar.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + expect(bar).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalled(); + expect(bar).toHaveBeenCalled(); + }); + + it("should pass arguments specified in @HostListener('asdf', [...args])", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf", ["1", "2", "null"]) + adsf() { + foo.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalledWith(1,2,null); + }); + + it("should support the $event argument in @HostListener('asdf', ['$event'])", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf", ["$event"]) + adsf() { + foo.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalledWith(jasmine.objectContaining({type: "asdf", target: $dom[0]})); + }); + + it("should support expressions in @HostListener args)", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf", ["$event.target", "1+2"]) + adsf() { + foo.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalledWith($dom[0], 3); + }); + + it("should not provide access to values on the $scope", function() { + const foo = jasmine.createSpy("foo event callback"); + + @Component({ + selector: "comp" + }) + class Comp { + @HostListener("asdf", ["$root", "$id", "$ctrl"]) + adsf() { + foo.apply(this, arguments); + } + } + + @NgModule({id: "compMod", declarations: [Comp]}) + class Mod {} + + const {$dom} = bootstrapAndCompile("compMod", ""); + + expect(foo).not.toHaveBeenCalled(); + $dom.triggerHandler("asdf"); + expect(foo).toHaveBeenCalledWith(undefined, undefined, undefined); + }); + }); }); describe("@Directive", function() { diff --git a/src/facade.ts b/src/facade.ts index 6cb80d8..575988d 100644 --- a/src/facade.ts +++ b/src/facade.ts @@ -11,7 +11,6 @@ import {module, noop, extend} from "angular"; - component lifecycle interfaces: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html ? - @Optional, @Self, @SkipSelf, @Host - @ViewChild ? - - @HostListener ? (https://github.com/angular/angular/blob/2.4.5/modules/%40angular/core/src/metadata/directives.ts#L1005) - ElementRef ? (https://github.com/angular/angular/blob/2.4.5/modules/%40angular/core/src/linker/element_ref.ts#L24) */ @@ -403,6 +402,34 @@ export class EventEmitter { } +/** + * @HostListener + * + * Bind a DOM event to the host element. + * + * https://angular.io/docs/ts/latest/api/core/index/HostListener-interface.html + */ +//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) { + //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 + const argValues = argExps.map((argExp) => argExp({$event})); + + this[propertyKey](...argValues); + }); + } + HostListenerSetup.$inject = ["$element", "$parse", "$injector"]; + + addPreLink(targetPrototype, HostListenerSetup); + }; +} + + /** * @Require *