diff --git a/modules/angular2/src/core/compiler/template_parser.ts b/modules/angular2/src/core/compiler/template_parser.ts index 452e45a3ef..4817ac6202 100644 --- a/modules/angular2/src/core/compiler/template_parser.ts +++ b/modules/angular2/src/core/compiler/template_parser.ts @@ -220,7 +220,8 @@ class TemplateParseVisitor implements HtmlAstVisitor { parsedElement = new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceInfo); } else if (isTemplateElement) { - this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events, + this._assertAllEventsPublishedByDirectives(directives, events, element.sourceInfo); + this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, element.sourceInfo); parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children, elementNgContentIndex, element.sourceInfo); @@ -239,7 +240,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts( element.name, templateElementOrDirectiveProps, templateDirectives); this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps, - [], element.sourceInfo); + element.sourceInfo); parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement], component.findNgContentIndex(templateCssSelector), element.sourceInfo); @@ -567,7 +568,6 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[], elementProps: BoundElementPropertyAst[], - events: BoundEventAst[], sourceInfo: string) { var componentTypeNames: string[] = this._findComponentDirectiveNames(directives); if (componentTypeNames.length > 0) { @@ -578,9 +578,20 @@ class TemplateParseVisitor implements HtmlAstVisitor { this._reportError( `Property binding ${prop.name} not used by any directive on an embedded template in ${prop.sourceInfo}`); }); + } + + private _assertAllEventsPublishedByDirectives(directives: DirectiveAst[], events: BoundEventAst[], + sourceInfo: string) { + var allDirectiveEvents = new Set(); + directives.forEach(directive => { + StringMapWrapper.forEach(directive.directive.outputs, + (eventName, _) => { allDirectiveEvents.add(eventName); }); + }); events.forEach(event => { - this._reportError( - `Event binding ${event.name} on an embedded template in ${event.sourceInfo}`); + if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) { + this._reportError( + `Event binding ${event.fullName} not emitted by any directive on an embedded template in ${sourceInfo}`); + } }); } } diff --git a/modules/angular2/test/core/compiler/template_parser_spec.ts b/modules/angular2/test/core/compiler/template_parser_spec.ts index d1a285fad6..bae38463be 100644 --- a/modules/angular2/test/core/compiler/template_parser_spec.ts +++ b/modules/angular2/test/core/compiler/template_parser_spec.ts @@ -267,6 +267,19 @@ export function main() { ]); }); + it('should allow events on explicit embedded templates that are emitted by a directive', + () => { + var dirA = CompileDirectiveMetadata.create({ + selector: 'template', + outputs: ['e'], + type: new CompileTypeMetadata({name: 'DirA'}) + }); + expect(humanizeTemplateAsts(parse('', [dirA]))) + .toEqual([ + [EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'], + [DirectiveAst, dirA, 'TestComp > template:nth-child(0)'], + ]); + }); }); describe('bindon', () => { @@ -804,7 +817,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-ch More than one component: DirB,DirA in TestComp > div:nth-child(0)`); }); - it('should not allow components or element nor event bindings on explicit embedded templates', + it('should not allow components or element bindings nor dom events on explicit embedded templates', () => { var dirA = CompileDirectiveMetadata.create({ selector: '[a]', @@ -814,9 +827,9 @@ More than one component: DirB,DirA in TestComp > div:nth-child(0)`); }); expect(() => parse('', [dirA])) .toThrowError(`Template parse errors: +Event binding e not emitted by any directive on an embedded template in TestComp > template:nth-child(0) Components on an embedded template: DirA in TestComp > template:nth-child(0) -Property binding a not used by any directive on an embedded template in TestComp > template:nth-child(0)[[a]=b] -Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)=f]`); +Property binding a not used by any directive on an embedded template in TestComp > template:nth-child(0)[[a]=b]`); }); it('should not allow components or element bindings on inline embedded templates', () => { diff --git a/modules/angular2/test/core/linker/integration_spec.ts b/modules/angular2/test/core/linker/integration_spec.ts index 29bb364f16..3ad977abf4 100644 --- a/modules/angular2/test/core/linker/integration_spec.ts +++ b/modules/angular2/test/core/linker/integration_spec.ts @@ -800,7 +800,7 @@ export function main() { }); })); - it('should support events via EventEmitter', + it('should support events via EventEmitter on regular elements', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new ViewMetadata({ template: '
', @@ -825,6 +825,31 @@ export function main() { }); })); + it('should support events via EventEmitter on template elements', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new ViewMetadata({ + template: '', + directives: [DirectiveEmitingEvent, DirectiveListeningEvent] + })) + + .createAsync(MyComp) + .then((rootTC) => { + + var tc = rootTC.debugElement.componentViewChildren[0]; + var emitter = tc.inject(DirectiveEmitingEvent); + var listener = tc.inject(DirectiveListeningEvent); + + expect(listener.msg).toEqual(''); + + ObservableWrapper.subscribe(emitter.event, (_) => { + expect(listener.msg).toEqual('fired !'); + async.done(); + }); + + emitter.fireEvent('fired !'); + }); + })); + it('should support [()] syntax', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new ViewMetadata({