feat(core): add support for @HostBinding and @HostListener

Example:

@Directive({selector: 'my-directive'})
class MyDirective {
  @HostBinding("attr.my-attr") myAttr: string;
  @HostListener("click", ["$event.target"])
  onClick(target) {
    this.target = target;
  }
}

Closes #3996
This commit is contained in:
vsavkin
2015-09-04 14:07:16 -07:00
committed by Victor Savkin
parent 855cb16cc7
commit df8e15cab7
7 changed files with 264 additions and 30 deletions

View File

@ -1,6 +1,13 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveMetadata, Directive, Property, Event} from 'angular2/metadata';
import {
DirectiveMetadata,
Directive,
Property,
Event,
HostBinding,
HostListener
} from 'angular2/metadata';
@Directive({selector: 'someDirective'})
class SomeDirective {
@ -11,7 +18,7 @@ class SomeChildDirective extends SomeDirective {
}
@Directive({selector: 'someDirective', properties: ['c']})
class SomeDirectiveWithProps {
class SomeDirectiveWithProperties {
@Property() a;
@Property("renamed") b;
c;
@ -40,6 +47,23 @@ class SomeDirectiveWithGetterEvents {
}
}
@Directive({selector: 'someDirective', host: {'[c]': 'c'}})
class SomeDirectiveWithHostBindings {
@HostBinding() a;
@HostBinding("renamed") b;
c;
}
@Directive({selector: 'someDirective', host: {'(c)': 'onC()'}})
class SomeDirectiveWithHostListeners {
@HostListener('a')
onA() {
}
@HostListener('b', ['$event.value'])
onB(value) {
}
}
class SomeDirectiveWithoutMetadata {}
@ -52,7 +76,8 @@ export function main() {
it('should read out the Directive metadata', () => {
var directiveMetadata = resolver.resolve(SomeDirective);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata({selector: 'someDirective', properties: [], events: []}));
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: [], events: [], host: {}}));
});
it('should throw if not matching metadata is found', () => {
@ -63,39 +88,44 @@ export function main() {
it('should not read parent class Directive metadata', function() {
var directiveMetadata = resolver.resolve(SomeChildDirective);
expect(directiveMetadata)
.toEqual(
new DirectiveMetadata({selector: 'someChildDirective', properties: [], events: []}));
.toEqual(new DirectiveMetadata(
{selector: 'someChildDirective', properties: [], events: [], host: {}}));
});
describe('properties', () => {
it('should append directive properties', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithProps);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: ['c', 'a', 'b: renamed'], events: []}));
var directiveMetadata = resolver.resolve(SomeDirectiveWithProperties);
expect(directiveMetadata.properties).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithSetterProps);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: ['a: renamed'], events: []}));
expect(directiveMetadata.properties).toEqual(['a: renamed']);
});
});
describe('events', () => {
it('should append directive events', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithEvents);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: [], events: ['c', 'a', 'b: renamed']}));
expect(directiveMetadata.events).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithGetterEvents);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', properties: [], events: ['a: renamed']}));
expect(directiveMetadata.events).toEqual(['a: renamed']);
});
});
describe('host', () => {
it('should append host bindings', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithHostBindings);
expect(directiveMetadata.host).toEqual({'[c]': 'c', '[a]': 'a', '[renamed]': 'b'});
});
it('should append host listeners', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithHostListeners);
expect(directiveMetadata.host)
.toEqual({'(c)': 'onC()', '(a)': 'onA()', '(b)': 'onB($event.value)'});
});
});
});

View File

@ -73,7 +73,9 @@ import {
Query,
Pipe,
Property,
Event
Event,
HostBinding,
HostListener
} from 'angular2/metadata';
import {QueryList} from 'angular2/src/core/compiler/query_list';
@ -1625,6 +1627,24 @@ export function main() {
});
}));
it('should support host binding decorators',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<with-prop-decorators></with-prop-decorators>',
directives: [DirectiveWithPropDecorators]
}))
.createAsync(MyComp)
.then((rootTC) => {
rootTC.detectChanges();
var dir = rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
dir.myAttr = "aaa";
rootTC.detectChanges();
expect(DOM.getOuterHTML(rootTC.componentViewChildren[0].nativeElement))
.toContain('my-attr="aaa"');
async.done();
});
}));
if (DOM.supportsDOMEvents()) {
it('should support events decorators',
@ -1647,6 +1667,26 @@ export function main() {
expect(rootTC.componentInstance.ctxProp).toEqual("called");
})));
it('should support host listener decorators',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder,
async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<with-prop-decorators></with-prop-decorators>',
directives: [DirectiveWithPropDecorators]
}))
.createAsync(MyComp)
.then((rootTC) => {
rootTC.detectChanges();
var dir = rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
var native = rootTC.componentViewChildren[0].nativeElement;
native.click();
expect(dir.target).toBe(native);
async.done();
});
}));
}
});
});
@ -2203,8 +2243,16 @@ class DirectiveThrowingAnError {
@Directive({selector: 'with-prop-decorators'})
class DirectiveWithPropDecorators {
target;
@Property("elProp") dirProp: string;
@Event('elEvent') event = new EventEmitter();
@HostBinding("attr.my-attr") myAttr: string;
@HostListener("click", ["$event.target"])
onClick(target) {
this.target = target;
}
fireEvent(msg) { ObservableWrapper.callNext(this.event, msg); }
}