diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/content_projection/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/content_projection/TEST_CASES.json index 251c9d9d1c4..09eab5a7165 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/content_projection/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/content_projection/TEST_CASES.json @@ -99,8 +99,7 @@ "ng_project_as_selector.js" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should take the first selector if multiple values are passed into ngProjectAs", @@ -114,8 +113,7 @@ "ng_project_as_compound_selector.js" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should include parsed ngProjectAs selectors into template attrs", @@ -129,8 +127,7 @@ "ng_project_as_attribute.js" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should capture the node name of ng-content with a structural directive", diff --git a/packages/compiler/src/template/pipeline/src/phases/const_collection.ts b/packages/compiler/src/template/pipeline/src/phases/const_collection.ts index 7f5862a67cc..142791ccf30 100644 --- a/packages/compiler/src/template/pipeline/src/phases/const_collection.ts +++ b/packages/compiler/src/template/pipeline/src/phases/const_collection.ts @@ -11,6 +11,7 @@ import {splitNsName} from '../../../../ml_parser/tags'; import * as o from '../../../../output/output_ast'; import * as ir from '../../ir'; import {HostBindingCompilationJob, type CompilationJob, ComponentCompilationJob} from '../compilation'; +import {literalOrArrayLiteral} from '../conversion'; import {element} from '../instruction'; /** @@ -19,12 +20,12 @@ import {element} from '../instruction'; */ export function phaseConstCollection(job: CompilationJob): void { // Collect all extracted attributes. - const elementAttributes = new Map(); + const allElementAttributes = new Map(); for (const unit of job.units) { for (const op of unit.create) { if (op.kind === ir.OpKind.ExtractedAttribute) { - const attributes = elementAttributes.get(op.target) || new ElementAttributes(); - elementAttributes.set(op.target, attributes); + const attributes = allElementAttributes.get(op.target) || new ElementAttributes(); + allElementAttributes.set(op.target, attributes); attributes.add(op.bindingKind, op.name, op.expression); ir.OpList.remove(op); } @@ -36,7 +37,7 @@ export function phaseConstCollection(job: CompilationJob): void { for (const unit of job.units) { for (const op of unit.create) { if (ir.isElementOrContainerOp(op)) { - const attributes = elementAttributes.get(op.xref); + const attributes = allElementAttributes.get(op.xref); if (attributes !== undefined) { const attrArray = serializeAttributes(attributes); if (attrArray.entries.length > 0) { @@ -49,7 +50,7 @@ export function phaseConstCollection(job: CompilationJob): void { } else if (job instanceof HostBindingCompilationJob) { // TODO: If the host binding case further diverges, we may want to split it into its own // phase. - for (const [xref, attributes] of elementAttributes.entries()) { + for (const [xref, attributes] of allElementAttributes.entries()) { if (xref !== job.root.xref) { throw new Error( `An attribute would be const collected into the host binding's template function, but is not associated with the root xref.`); @@ -105,6 +106,18 @@ class ElementAttributes { return; } this.known.add(name); + if (name === 'ngProjectAs') { + // TODO: Perhaps we should consider ingesting raw attribute strings instead of expressions, in + // order to avoid this messy unboxing? + if (value === null || !(value instanceof o.LiteralExpr)) { + throw Error('ngProjectAs must have a string literal value'); + } + this.projectAs = value.value as string; + // TODO: TemplateDefinitionBuilder allows `ngProjectAs` to also be assigned as a literal + // attribute. Is this sane? + } + + const array = this.arrayFor(kind); array.push(...getAttributeNameLiterals(name)); if (kind === ir.BindingKind.Attribute || kind === ir.BindingKind.StyleProperty) { @@ -147,7 +160,11 @@ function serializeAttributes({attributes, bindings, classes, i18n, projectAs, st const attrArray = [...attributes]; if (projectAs !== null) { - attrArray.push(o.literal(core.AttributeMarker.ProjectAs), o.literal(projectAs)); + // Parse the attribute value into a CssSelectorList. Note that we only take the + // first selector, because we don't support multiple selectors in ngProjectAs. + const parsedR3Selector = core.parseSelectorToR3Selector(projectAs)[0]; + attrArray.push( + o.literal(core.AttributeMarker.ProjectAs), literalOrArrayLiteral(parsedR3Selector)); } if (classes.length > 0) { attrArray.push(o.literal(core.AttributeMarker.Classes), ...classes);