Skip to content

Commit

Permalink
feat(HostListener): add component HostListener support
Browse files Browse the repository at this point in the history
Closes #6
  • Loading branch information
jbedard committed Feb 19, 2017
1 parent ece77e3 commit 84e554b
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 2 deletions.
149 changes: 148 additions & 1 deletion src/facade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_";
Expand Down Expand Up @@ -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", "<comp>");

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", "<comp>");

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", "<comp>");

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", "<comp>");

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", "<comp>");

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", "<comp>");

expect(foo).not.toHaveBeenCalled();
$dom.triggerHandler("asdf");
expect(foo).toHaveBeenCalledWith(undefined, undefined, undefined);
});
});
});

describe("@Directive", function() {
Expand Down
29 changes: 28 additions & 1 deletion src/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/

Expand Down Expand Up @@ -403,6 +402,34 @@ export class EventEmitter<T> {
}


/**
* @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
*
Expand Down

0 comments on commit 84e554b

Please sign in to comment.