diff --git a/crates/oxc_angular_compiler/angular b/crates/oxc_angular_compiler/angular index ebd698b90..1cb0524f8 160000 --- a/crates/oxc_angular_compiler/angular +++ b/crates/oxc_angular_compiler/angular @@ -1 +1 @@ -Subproject commit ebd698b90f097bafb6a9696bc4f14f9ef1a7da05 +Subproject commit 1cb0524f82ebcc06642ceebd0b96a19bba883b2e diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs index db58bfbbb..72d2919b6 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs @@ -760,7 +760,7 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { // Second pass (2b): Assign const indices in collected order // This is where we actually call add_const, matching Angular's getConstIndex call order let mut element_const_indices: FxHashMap = FxHashMap::default(); - let mut projection_attrs: FxHashMap> = FxHashMap::default(); + let mut projection_attrs: FxHashMap> = FxHashMap::default(); for xref_item in &xrefs_to_assign { match xref_item { @@ -768,7 +768,21 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { if let Some(attrs) = all_element_attrs.get(xref) { if !attrs.is_empty() { let attr_array = serialize_attributes_to_array_expr(allocator, attrs); - projection_attrs.insert(*xref, attr_array); + // Angular v22 (rc.3+) pools projection attributes into the + // shared const pool (`_cN`) instead of emitting them inline, + // matching `getConstLiteral(attrArray, true)` in Angular's + // const_collection.ts (angular/angular@2891f7e). This phase + // runs after `generate_projection_def`, so the projectionDef + // and ngContentSelectors consts are pooled first and the + // projection attrs land at the next `_cN`, as in the goldens. + let pooled = job.pool.get_const_literal(attr_array, true); + let pooled_name = match pooled { + OutputExpression::ReadVar(rv) => rv.name.clone(), + _ => unreachable!( + "ConstantPool::get_const_literal must return OutputExpression::ReadVar" + ), + }; + projection_attrs.insert(*xref, pooled_name); } } } @@ -794,8 +808,17 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { for op in view.create.iter_mut() { match op { CreateOp::Projection(proj) => { - if let Some(attrs) = projection_attrs.remove(&proj.xref) { - proj.attributes = Some(attrs); + // It's possible for multiple projection ops to reference the same xref + // (e.g. across conditional branches). Avoid consuming the pooled attrs so + // every matching projection op receives the same const reference. + if let Some(pooled_name) = projection_attrs.get(&proj.xref) { + proj.attributes = Some(OutputExpression::ReadVar(Box::new_in( + crate::output::ast::ReadVarExpr { + name: pooled_name.clone(), + source_span: None, + }, + allocator, + ))); } } CreateOp::ElementStart(elem) => { diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap index ddaa731f2..49e913b64 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["[select]","'[slot=expanded-content]'"]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["[select]","'[slot=expanded-content]'"]); + i0.ɵɵprojection(0,0,_c1); } } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap index 965876452..ca2c57572 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["ngProjectAs","bit-label",5,["bit-label"]]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["ngProjectAs","bit-label",5,["bit-label"]]); + i0.ɵɵprojection(0,0,_c1); } } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap index 470db9dad..055836b7f 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["ngProjectAs","[card-content]",5,["","card-content",""]]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["ngProjectAs","[card-content]",5,["","card-content",""]]); + i0.ɵɵprojection(0,0,_c1); } }