fix(compiler-cli): deduplicate deferred imports across multiple blocks

When @Component.deferredImports is defined as an object mapping block
names to dependency arrays, deduplicate entries when aggregating block
imports into the component-level flattened scope. This prevents
duplicate directive/component collisions in DirectiveMatcher when
multiple @defer blocks share dependencies.
This commit is contained in:
Alex Rickabaugh
2026-08-20 21:11:02 +00:00
committed by Leon Senft
parent 7d9f55da11
commit 2e2c426e76
2 changed files with 45 additions and 1 deletions
@@ -120,7 +120,11 @@ export function validateAndFlattenComponentImports(
validateAndFlattenComponentImports(blockValue, propExpr, isDeferred);
diagnostics.push(...blockDiagnostics);
flattened.push(...blockImports);
for (const blockImport of blockImports) {
if (!flattened.some((existing) => existing.node === blockImport.node)) {
flattened.push(blockImport);
}
}
importsByBlock.set(blockName, blockImports);
}
return {imports: flattened, importsByBlock, diagnostics};
@@ -2547,6 +2547,46 @@ runInEachFileSystem(() => {
"Directive 'DirB' (used on element 'div') was imported via `@Component.deferredImports` under block 'blockB', but is used in a `@defer` block configured for 'blockC'",
);
});
it('should allow the same dependency across multiple defer blocks without duplicate symbol diagnostics', () => {
env.write(
'dirs.ts',
`
import { Directive } from '@angular/core';
@Directive({ selector: '[dirA]' })
export class DirA {}
`,
);
env.write(
'/test.ts',
`
import { Component } from '@angular/core';
import { DirA } from './dirs';
@Component({
selector: 'test-cmp',
// @ts-ignore
deferredImports: {
blockA: [DirA],
blockB: [DirA],
},
template: \`
@defer (name blockA) {
<div dirA></div>
}
@defer (name blockB) {
<div dirA></div>
}
\`,
})
export class TestCmp {}
`,
);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
});
});
});