X Tutup
Skip to content

Commit cec8b58

Browse files
committed
fix(compiler): explicitly support event bindings also on <template> elements
Although these events don’t fire events themselves, there might be directives on them that fire events. Closes #4712
1 parent b89c5bc commit cec8b58

File tree

3 files changed

+58
-9
lines changed

3 files changed

+58
-9
lines changed

modules/angular2/src/core/compiler/template_parser.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
220220
parsedElement =
221221
new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceInfo);
222222
} else if (isTemplateElement) {
223-
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events,
223+
this._assertAllEventsPublishedByDirectives(directives, events, element.sourceInfo);
224+
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps,
224225
element.sourceInfo);
225226
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
226227
elementNgContentIndex, element.sourceInfo);
@@ -239,7 +240,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
239240
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
240241
element.name, templateElementOrDirectiveProps, templateDirectives);
241242
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
242-
[], element.sourceInfo);
243+
element.sourceInfo);
243244
parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement],
244245
component.findNgContentIndex(templateCssSelector),
245246
element.sourceInfo);
@@ -567,7 +568,6 @@ class TemplateParseVisitor implements HtmlAstVisitor {
567568

568569
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
569570
elementProps: BoundElementPropertyAst[],
570-
events: BoundEventAst[],
571571
sourceInfo: string) {
572572
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
573573
if (componentTypeNames.length > 0) {
@@ -578,9 +578,20 @@ class TemplateParseVisitor implements HtmlAstVisitor {
578578
this._reportError(
579579
`Property binding ${prop.name} not used by any directive on an embedded template in ${prop.sourceInfo}`);
580580
});
581+
}
582+
583+
private _assertAllEventsPublishedByDirectives(directives: DirectiveAst[], events: BoundEventAst[],
584+
sourceInfo: string) {
585+
var allDirectiveEvents = new Set<string>();
586+
directives.forEach(directive => {
587+
StringMapWrapper.forEach(directive.directive.outputs,
588+
(eventName, _) => { allDirectiveEvents.add(eventName); });
589+
});
581590
events.forEach(event => {
582-
this._reportError(
583-
`Event binding ${event.name} on an embedded template in ${event.sourceInfo}`);
591+
if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) {
592+
this._reportError(
593+
`Event binding ${event.fullName} not emitted by any directive on an embedded template in ${sourceInfo}`);
594+
}
584595
});
585596
}
586597
}

modules/angular2/test/core/compiler/template_parser_spec.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,19 @@ export function main() {
267267
]);
268268
});
269269

270+
it('should allow events on explicit embedded templates that are emitted by a directive',
271+
() => {
272+
var dirA = CompileDirectiveMetadata.create({
273+
selector: 'template',
274+
outputs: ['e'],
275+
type: new CompileTypeMetadata({name: 'DirA'})
276+
});
277+
expect(humanizeTemplateAsts(parse('<template (e)="f"></template>', [dirA])))
278+
.toEqual([
279+
[EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'],
280+
[DirectiveAst, dirA, 'TestComp > template:nth-child(0)'],
281+
]);
282+
});
270283
});
271284

272285
describe('bindon', () => {
@@ -804,7 +817,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-ch
804817
More than one component: DirB,DirA in TestComp > div:nth-child(0)`);
805818
});
806819

807-
it('should not allow components or element nor event bindings on explicit embedded templates',
820+
it('should not allow components or element bindings nor dom events on explicit embedded templates',
808821
() => {
809822
var dirA = CompileDirectiveMetadata.create({
810823
selector: '[a]',
@@ -814,9 +827,9 @@ More than one component: DirB,DirA in TestComp > div:nth-child(0)`);
814827
});
815828
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
816829
.toThrowError(`Template parse errors:
830+
Event binding e not emitted by any directive on an embedded template in TestComp > template:nth-child(0)
817831
Components on an embedded template: DirA in TestComp > template:nth-child(0)
818-
Property binding a not used by any directive on an embedded template in TestComp > template:nth-child(0)[[a]=b]
819-
Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)=f]`);
832+
Property binding a not used by any directive on an embedded template in TestComp > template:nth-child(0)[[a]=b]`);
820833
});
821834

822835
it('should not allow components or element bindings on inline embedded templates', () => {

modules/angular2/test/core/linker/integration_spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ export function main() {
800800
});
801801
}));
802802

803-
it('should support events via EventEmitter',
803+
it('should support events via EventEmitter on regular elements',
804804
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
805805
tcb.overrideView(MyComp, new ViewMetadata({
806806
template: '<div emitter listener></div>',
@@ -825,6 +825,31 @@ export function main() {
825825
});
826826
}));
827827

828+
it('should support events via EventEmitter on template elements',
829+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
830+
tcb.overrideView(MyComp, new ViewMetadata({
831+
template: '<template emitter listener></template>',
832+
directives: [DirectiveEmitingEvent, DirectiveListeningEvent]
833+
}))
834+
835+
.createAsync(MyComp)
836+
.then((rootTC) => {
837+
838+
var tc = rootTC.debugElement.componentViewChildren[0];
839+
var emitter = tc.inject(DirectiveEmitingEvent);
840+
var listener = tc.inject(DirectiveListeningEvent);
841+
842+
expect(listener.msg).toEqual('');
843+
844+
ObservableWrapper.subscribe(emitter.event, (_) => {
845+
expect(listener.msg).toEqual('fired !');
846+
async.done();
847+
});
848+
849+
emitter.fireEvent('fired !');
850+
});
851+
}));
852+
828853
it('should support [()] syntax',
829854
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
830855
tcb.overrideView(MyComp, new ViewMetadata({

0 commit comments

Comments
 (0)
X Tutup