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
This commit is contained in:
parent
b89c5bc581
commit
cec8b58373
@ -220,7 +220,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
parsedElement =
|
parsedElement =
|
||||||
new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceInfo);
|
new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceInfo);
|
||||||
} else if (isTemplateElement) {
|
} else if (isTemplateElement) {
|
||||||
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events,
|
this._assertAllEventsPublishedByDirectives(directives, events, element.sourceInfo);
|
||||||
|
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps,
|
||||||
element.sourceInfo);
|
element.sourceInfo);
|
||||||
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
|
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
|
||||||
elementNgContentIndex, element.sourceInfo);
|
elementNgContentIndex, element.sourceInfo);
|
||||||
@ -239,7 +240,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||||
element.name, templateElementOrDirectiveProps, templateDirectives);
|
element.name, templateElementOrDirectiveProps, templateDirectives);
|
||||||
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
|
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
|
||||||
[], element.sourceInfo);
|
element.sourceInfo);
|
||||||
parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement],
|
parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement],
|
||||||
component.findNgContentIndex(templateCssSelector),
|
component.findNgContentIndex(templateCssSelector),
|
||||||
element.sourceInfo);
|
element.sourceInfo);
|
||||||
@ -567,7 +568,6 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
|
|
||||||
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
|
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
|
||||||
elementProps: BoundElementPropertyAst[],
|
elementProps: BoundElementPropertyAst[],
|
||||||
events: BoundEventAst[],
|
|
||||||
sourceInfo: string) {
|
sourceInfo: string) {
|
||||||
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
|
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
|
||||||
if (componentTypeNames.length > 0) {
|
if (componentTypeNames.length > 0) {
|
||||||
@ -578,9 +578,20 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
this._reportError(
|
this._reportError(
|
||||||
`Property binding ${prop.name} not used by any directive on an embedded template in ${prop.sourceInfo}`);
|
`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<string>();
|
||||||
|
directives.forEach(directive => {
|
||||||
|
StringMapWrapper.forEach(directive.directive.outputs,
|
||||||
|
(eventName, _) => { allDirectiveEvents.add(eventName); });
|
||||||
|
});
|
||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
this._reportError(
|
if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) {
|
||||||
`Event binding ${event.name} on an embedded template in ${event.sourceInfo}`);
|
this._reportError(
|
||||||
|
`Event binding ${event.fullName} not emitted by any directive on an embedded template in ${sourceInfo}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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('<template (e)="f"></template>', [dirA])))
|
||||||
|
.toEqual([
|
||||||
|
[EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'],
|
||||||
|
[DirectiveAst, dirA, 'TestComp > template:nth-child(0)'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('bindon', () => {
|
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)`);
|
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({
|
var dirA = CompileDirectiveMetadata.create({
|
||||||
selector: '[a]',
|
selector: '[a]',
|
||||||
@ -814,9 +827,9 @@ More than one component: DirB,DirA in TestComp > div:nth-child(0)`);
|
|||||||
});
|
});
|
||||||
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
|
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
|
||||||
.toThrowError(`Template parse errors:
|
.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)
|
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]
|
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]`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow components or element bindings on inline embedded templates', () => {
|
it('should not allow components or element bindings on inline embedded templates', () => {
|
||||||
|
@ -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) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
tcb.overrideView(MyComp, new ViewMetadata({
|
tcb.overrideView(MyComp, new ViewMetadata({
|
||||||
template: '<div emitter listener></div>',
|
template: '<div emitter listener></div>',
|
||||||
@ -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: '<template emitter listener></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',
|
it('should support [()] syntax',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
tcb.overrideView(MyComp, new ViewMetadata({
|
tcb.overrideView(MyComp, new ViewMetadata({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user