fix(ivy): include ngProjectAs into attributes array (#32784)
				
					
				
			Prior to this commit, the `ngProjectAs` attribute was only included with a special flag and in a parsed format. As a result, projected node was missing `ngProjectAs` attribute as well as other attributes added after `ngProjectAs` one. This is problematic since app code might rely on the presence of `ngProjectAs` attribute (for example in CSS). This commit fixes the problem by including `ngProjectAs` into attributes array as a regular attribute and also makes sure that the parsed version of the `ngProjectAs` attribute with a special marker is added after regular attributes (thus we set them correctly at runtime). This change also aligns View Engine and Ivy behavior. PR Close #32784
This commit is contained in:
		
							parent
							
								
									278d634723
								
							
						
					
					
						commit
						966c2a326a
					
				| @ -1376,7 +1376,7 @@ describe('compiler compliance', () => { | ||||
|         const SimpleComponentDefinition = ` | ||||
|           const $_c0$ = [[["", "title", ""]]]; | ||||
|           const $_c1$ = ["[title]"]; | ||||
|           const $_c2$ = [5, ["", "title", ""]]; | ||||
|           const $_c2$ = ["ngProjectAs", "[title]", 5, ["", "title", ""]]; | ||||
|           … | ||||
|           MyApp.ngComponentDef = $r3$.ɵɵdefineComponent({ | ||||
|             type: MyApp, | ||||
| @ -1428,7 +1428,7 @@ describe('compiler compliance', () => { | ||||
|         const SimpleComponentDefinition = ` | ||||
|           const $_c0$ = [[["", "title", ""]]]; | ||||
|           const $_c1$ = ["[title]"]; | ||||
|           const $_c2$ = [5, ["", "title", ""]]; | ||||
|           const $_c2$ = ["ngProjectAs", "[title],[header]", 5, ["", "title", ""]]; | ||||
|           … | ||||
|           MyApp.ngComponentDef = $r3$.ɵɵdefineComponent({ | ||||
|             type: MyApp, | ||||
|  | ||||
| @ -497,18 +497,24 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|     const projectionSlotIdx = this._ngContentSelectorsOffset + this._ngContentReservedSlots.length; | ||||
|     const parameters: o.Expression[] = [o.literal(slot)]; | ||||
|     const attributes: o.Expression[] = []; | ||||
|     let ngProjectAsAttr: t.TextAttribute|undefined; | ||||
| 
 | ||||
|     this._ngContentReservedSlots.push(ngContent.selector); | ||||
| 
 | ||||
|     ngContent.attributes.forEach((attribute) => { | ||||
|       const {name, value} = attribute; | ||||
|       if (name === NG_PROJECT_AS_ATTR_NAME) { | ||||
|         attributes.push(...getNgProjectAsLiteral(attribute)); | ||||
|       } else if (name.toLowerCase() !== NG_CONTENT_SELECT_ATTR) { | ||||
|         ngProjectAsAttr = attribute; | ||||
|       } | ||||
|       if (name.toLowerCase() !== NG_CONTENT_SELECT_ATTR) { | ||||
|         attributes.push(o.literal(name), o.literal(value)); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     if (ngProjectAsAttr) { | ||||
|       attributes.push(...getNgProjectAsLiteral(ngProjectAsAttr)); | ||||
|     } | ||||
| 
 | ||||
|     if (attributes.length > 0) { | ||||
|       parameters.push(o.literal(projectionSlotIdx), o.literalArr(attributes)); | ||||
|     } else if (projectionSlotIdx !== 0) { | ||||
| @ -535,6 +541,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
| 
 | ||||
|     const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = []; | ||||
|     const outputAttrs: t.TextAttribute[] = []; | ||||
|     let ngProjectAsAttr: t.TextAttribute|undefined; | ||||
| 
 | ||||
|     const [namespaceKey, elementName] = splitNsName(element.name); | ||||
|     const isNgContainer = checkIsNgContainer(element.name); | ||||
| @ -549,6 +556,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|       } else if (name === 'class') { | ||||
|         stylingBuilder.registerClassAttr(value); | ||||
|       } else { | ||||
|         if (attr.name === NG_PROJECT_AS_ATTR_NAME) { | ||||
|           ngProjectAsAttr = attr; | ||||
|         } | ||||
|         if (attr.i18n) { | ||||
|           // Place attributes into a separate array for i18n processing, but also keep such
 | ||||
|           // attributes in the main list to make them available for directive matching at runtime.
 | ||||
| @ -590,16 +600,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|     }); | ||||
| 
 | ||||
|     outputAttrs.forEach(attr => { | ||||
|       if (attr.name === NG_PROJECT_AS_ATTR_NAME) { | ||||
|         attributes.push(...getNgProjectAsLiteral(attr)); | ||||
|       } else { | ||||
|         attributes.push(...getAttributeNameLiterals(attr.name), o.literal(attr.value)); | ||||
|       } | ||||
|       attributes.push(...getAttributeNameLiterals(attr.name), o.literal(attr.value)); | ||||
|     }); | ||||
| 
 | ||||
|     // add attributes for directive and projection matching purposes
 | ||||
|     attributes.push(...this.prepareNonRenderAttrs( | ||||
|         allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs)); | ||||
|         allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs, ngProjectAsAttr)); | ||||
|     parameters.push(this.toAttrsParam(attributes)); | ||||
| 
 | ||||
|     // local refs (ex.: <div #foo #bar="baz">)
 | ||||
| @ -1206,6 +1212,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|    *   STYLES, style1, value1, style2, value2, | ||||
|    *   BINDINGS, name1, name2, name3, | ||||
|    *   TEMPLATE, name4, name5, name6, | ||||
|    *   PROJECT_AS, selector, | ||||
|    *   I18N, name7, name8, ...] | ||||
|    * ``` | ||||
|    * | ||||
| @ -1215,7 +1222,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|   private prepareNonRenderAttrs( | ||||
|       inputs: t.BoundAttribute[], outputs: t.BoundEvent[], styles?: StylingBuilder, | ||||
|       templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [], | ||||
|       i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] { | ||||
|       i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = [], | ||||
|       ngProjectAsAttr?: t.TextAttribute): o.Expression[] { | ||||
|     const alreadySeen = new Set<string>(); | ||||
|     const attrExprs: o.Expression[] = []; | ||||
| 
 | ||||
| @ -1271,6 +1279,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|       templateAttrs.forEach(attr => addAttrExpr(attr.name)); | ||||
|     } | ||||
| 
 | ||||
|     if (ngProjectAsAttr) { | ||||
|       attrExprs.push(...getNgProjectAsLiteral(ngProjectAsAttr)); | ||||
|     } | ||||
| 
 | ||||
|     if (i18nAttrs.length) { | ||||
|       attrExprs.push(o.literal(core.AttributeMarker.I18n)); | ||||
|       i18nAttrs.forEach(attr => addAttrExpr(attr.name)); | ||||
|  | ||||
| @ -1025,6 +1025,36 @@ describe('projection', () => { | ||||
|     expect(fixture.nativeElement.textContent).not.toContain('Title content'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should preserve ngProjectAs and other attributes on projected element', () => { | ||||
|     @Component({ | ||||
|       selector: 'projector', | ||||
|       template: `<ng-content select="projectMe"></ng-content>`, | ||||
|     }) | ||||
|     class Projector { | ||||
|     } | ||||
| 
 | ||||
|     @Component({ | ||||
|       template: ` | ||||
|         <projector> | ||||
|           <div ngProjectAs="projectMe" title="some title"></div> | ||||
|         </projector> | ||||
|       ` | ||||
|     }) | ||||
|     class Root { | ||||
|     } | ||||
| 
 | ||||
|     TestBed.configureTestingModule({ | ||||
|       declarations: [Root, Projector], | ||||
|     }); | ||||
|     const fixture = TestBed.createComponent(Root); | ||||
|     fixture.detectChanges(); | ||||
| 
 | ||||
|     const projectedElement = fixture.debugElement.query(By.css('div')); | ||||
|     const {ngProjectAs, title} = projectedElement.attributes; | ||||
|     expect(ngProjectAs).toBe('projectMe'); | ||||
|     expect(title).toBe('some title'); | ||||
|   }); | ||||
| 
 | ||||
|   describe('on inline templates (e.g.  *ngIf)', () => { | ||||
|     it('should work when matching the element name', () => { | ||||
|       let divDirectives = 0; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user