Skip to content

Commit c086653

Browse files
JoostKAndrewKushnir
authored andcommitted
fix(core): deduplicate imports of standalone components in JIT compiler (#46439)
During JIT compilation of standalone components the compiler did not deduplicate declarations in the imports array, unlike the AOT compiler. This may result in runtime errors during directive matching, when the same component is found multiple times resulting in NG0300, for example: > NG0300: Multiple components match node with tagname mat-form-field: MatFormField and MatFormField. This commit fixes the issue by deduplicating imports in the JIT compiler. Relates to #46109 (comment) Closes #46109 PR Close #46439
1 parent 3dd7bb3 commit c086653

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

packages/core/src/render3/jit/directive.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,23 @@ function getStandaloneDefFunctions(type: Type<any>, imports: Type<any>[]): {
246246
// Standalone components are always able to self-reference, so include the component's own
247247
// definition in its `directiveDefs`.
248248
cachedDirectiveDefs = [getComponentDef(type)!];
249+
const seen = new Set<Type<unknown>>();
249250

250251
for (const rawDep of imports) {
251252
ngDevMode && verifyStandaloneImport(rawDep, type);
252253

253254
const dep = resolveForwardRef(rawDep);
255+
if (seen.has(dep)) {
256+
continue;
257+
}
258+
seen.add(dep);
259+
254260
if (!!getNgModuleDef(dep)) {
255261
const scope = transitiveScopesFor(dep);
256262
for (const dir of scope.exported.directives) {
257263
const def = getComponentDef(dir) || getDirectiveDef(dir);
258-
if (def) {
264+
if (def && !seen.has(dir)) {
265+
seen.add(dir);
259266
cachedDirectiveDefs.push(def);
260267
}
261268
}
@@ -273,12 +280,24 @@ function getStandaloneDefFunctions(type: Type<any>, imports: Type<any>[]): {
273280
const pipeDefs = () => {
274281
if (cachedPipeDefs === null) {
275282
cachedPipeDefs = [];
283+
const seen = new Set<Type<unknown>>();
284+
276285
for (const rawDep of imports) {
277286
const dep = resolveForwardRef(rawDep);
287+
if (seen.has(dep)) {
288+
continue;
289+
}
290+
seen.add(dep);
278291

279292
if (!!getNgModuleDef(dep)) {
280293
const scope = transitiveScopesFor(dep);
281-
cachedPipeDefs.push(...Array.from(scope.exported.pipes).map(pipe => getPipeDef(pipe)!));
294+
for (const pipe of scope.exported.pipes) {
295+
const def = getPipeDef(pipe);
296+
if (def && !seen.has(pipe)) {
297+
seen.add(pipe);
298+
cachedPipeDefs.push(def);
299+
}
300+
}
282301
} else {
283302
const def = getPipeDef(dep);
284303
if (def) {

packages/core/test/acceptance/standalone_spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,42 @@ describe('standalone components, directives and pipes', () => {
453453
expect(fixture.nativeElement.innerHTML).toBe('<div red="true">blue</div>');
454454
});
455455

456+
it('should deduplicate declarations', () => {
457+
@Component({selector: 'test-red', standalone: true, template: 'red(<ng-content></ng-content>)'})
458+
class RedComponent {
459+
}
460+
461+
@Component({selector: 'test-blue', template: 'blue(<ng-content></ng-content>)'})
462+
class BlueComponent {
463+
}
464+
465+
@NgModule({declarations: [BlueComponent], exports: [BlueComponent]})
466+
class BlueModule {
467+
}
468+
469+
@NgModule({exports: [BlueModule]})
470+
class BlueAModule {
471+
}
472+
473+
@NgModule({exports: [BlueModule]})
474+
class BlueBModule {
475+
}
476+
477+
@Component({
478+
selector: 'standalone',
479+
standalone: true,
480+
template: `<test-red><test-blue>orange</test-blue></test-red>`,
481+
imports: [RedComponent, RedComponent, BlueAModule, BlueBModule],
482+
})
483+
class TestComponent {
484+
}
485+
486+
const fixture = TestBed.createComponent(TestComponent);
487+
fixture.detectChanges();
488+
expect(fixture.nativeElement.innerHTML)
489+
.toBe('<test-red>red(<test-blue>blue(orange)</test-blue>)</test-red>');
490+
});
491+
456492
it('should error when forwardRef does not resolve to a truthy value', () => {
457493
@Component({
458494
selector: 'test',

0 commit comments

Comments
 (0)