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 = | ||||
|           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<string>(); | ||||
|     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}`); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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', () => { | ||||
| @ -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('<template [a]="b" (e)="f"></template>', [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', () => { | ||||
|  | ||||
| @ -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: '<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', | ||||
|          inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { | ||||
|            tcb.overrideView(MyComp, new ViewMetadata({ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user