fix(core): don’t create a comment for components with empty template. (#15260)
Fixes #15143 PR Close #15260
This commit is contained in:
		
							parent
							
								
									aeb99645bb
								
							
						
					
					
						commit
						f8c075ae27
					
				| @ -166,10 +166,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     templateVisitAll(this, astNodes); |     templateVisitAll(this, astNodes); | ||||||
|     if (astNodes.length === 0 || |     if (this.parent && (astNodes.length === 0 || needsAdditionalRootNode(astNodes))) { | ||||||
|         (this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) { |       // if the view is an embedded view, then we need to add an additional root node in some cases
 | ||||||
|       // if the view is empty, or an embedded view has a view container as last root nde,
 |  | ||||||
|       // create an additional root node.
 |  | ||||||
|       this.nodes.push(() => ({ |       this.nodes.push(() => ({ | ||||||
|                         sourceSpan: null, |                         sourceSpan: null, | ||||||
|                         nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([ |                         nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([ | ||||||
| @ -971,19 +969,20 @@ function depDef(dep: CompileDiDependencyMetadata): o.Expression { | |||||||
|   return flags === DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]); |   return flags === DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function needsAdditionalRootNode(ast: TemplateAst): boolean { | function needsAdditionalRootNode(astNodes: TemplateAst[]): boolean { | ||||||
|   if (ast instanceof EmbeddedTemplateAst) { |   const lastAstNode = astNodes[astNodes.length - 1]; | ||||||
|     return ast.hasViewContainer; |   if (lastAstNode instanceof EmbeddedTemplateAst) { | ||||||
|  |     return lastAstNode.hasViewContainer; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (ast instanceof ElementAst) { |   if (lastAstNode instanceof ElementAst) { | ||||||
|     if (ast.name === NG_CONTAINER_TAG && ast.children.length) { |     if (lastAstNode.name === NG_CONTAINER_TAG && lastAstNode.children.length) { | ||||||
|       return needsAdditionalRootNode(ast.children[ast.children.length - 1]); |       return needsAdditionalRootNode(lastAstNode.children); | ||||||
|     } |     } | ||||||
|     return ast.hasViewContainer; |     return lastAstNode.hasViewContainer; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ast instanceof NgContentAst; |   return lastAstNode instanceof NgContentAst; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags { | function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags { | ||||||
|  | |||||||
| @ -24,10 +24,6 @@ export function viewDef( | |||||||
|     flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, |     flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, | ||||||
|     updateRenderer?: ViewUpdateFn): ViewDefinition { |     updateRenderer?: ViewUpdateFn): ViewDefinition { | ||||||
|   // clone nodes and set auto calculated values
 |   // clone nodes and set auto calculated values
 | ||||||
|   if (nodes.length === 0) { |  | ||||||
|     throw new Error(`Illegal State: Views without nodes are not allowed!`); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const reverseChildNodes: NodeDef[] = new Array(nodes.length); |   const reverseChildNodes: NodeDef[] = new Array(nodes.length); | ||||||
|   let viewBindingCount = 0; |   let viewBindingCount = 0; | ||||||
|   let viewDisposableCount = 0; |   let viewDisposableCount = 0; | ||||||
| @ -152,6 +148,9 @@ export function viewDef( | |||||||
| function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) { | function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) { | ||||||
|   const template = node.element && node.element.template; |   const template = node.element && node.element.template; | ||||||
|   if (template) { |   if (template) { | ||||||
|  |     if (!template.lastRenderRootNode) { | ||||||
|  |       throw new Error(`Illegal State: Embedded templates without nodes are not allowed!`); | ||||||
|  |     } | ||||||
|     if (template.lastRenderRootNode && |     if (template.lastRenderRootNode && | ||||||
|         template.lastRenderRootNode.flags & NodeFlags.EmbeddedViews) { |         template.lastRenderRootNode.flags & NodeFlags.EmbeddedViews) { | ||||||
|       throw new Error( |       throw new Error( | ||||||
|  | |||||||
| @ -306,6 +306,36 @@ function declareTests({useJit}: {useJit: boolean}) { | |||||||
|       ctx.detectChanges(); |       ctx.detectChanges(); | ||||||
|       expect(ctx.componentInstance.viewContainers.first).toBe(vc); |       expect(ctx.componentInstance.viewContainers.first).toBe(vc); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     describe('empty templates - #15143', () => { | ||||||
|  |       it('should allow empty components', () => { | ||||||
|  |         @Component({template: ''}) | ||||||
|  |         class MyComp { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const fixture = | ||||||
|  |             TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); | ||||||
|  |         fixture.detectChanges(); | ||||||
|  | 
 | ||||||
|  |         expect(fixture.debugElement.childNodes.length).toBe(0); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should allow empty embedded templates', () => { | ||||||
|  |         @Component({template: '<ng-template [ngIf]="true"></ng-template>'}) | ||||||
|  |         class MyComp { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const fixture = | ||||||
|  |             TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); | ||||||
|  |         fixture.detectChanges(); | ||||||
|  | 
 | ||||||
|  |         // Note: We always need to create at least a comment in an embedded template,
 | ||||||
|  |         // so we can append other templates after it.
 | ||||||
|  |         // 1 comment for the anchor,
 | ||||||
|  |         // 1 comment for the empty embedded template.
 | ||||||
|  |         expect(fixture.debugElement.childNodes.length).toBe(2); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user