diff --git a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts index 7f608c592a2..1a71661c351 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts @@ -717,20 +717,26 @@ export class ComponentDecoratorHandler implements symbol.usedPipes = declarations.filter(isUsedPipe).map(getSemanticReference); } - // Scan through the directives/pipes actually used in the template and check whether any - // import which needs to be generated would create a cycle. const cyclesFromDirectives = new Map(); const cyclesFromPipes = new Map(); - for (const usedDep of declarations) { - const cycle = this._checkForCyclicImport(usedDep.importedFile, usedDep.type, context); - if (cycle !== null) { - switch (usedDep.kind) { - case R3TemplateDependencyKind.Directive: - cyclesFromDirectives.set(usedDep, cycle); - break; - case R3TemplateDependencyKind.Pipe: - cyclesFromPipes.set(usedDep, cycle); - break; + + // Scan through the directives/pipes actually used in the template and check whether any + // import which needs to be generated would create a cycle. This check is skipped for + // standalone components as the dependencies of a standalone component have already been + // imported directly by the user, so Angular won't introduce any imports that aren't already + // in the user's program. + if (!metadata.isStandalone) { + for (const usedDep of declarations) { + const cycle = this._checkForCyclicImport(usedDep.importedFile, usedDep.type, context); + if (cycle !== null) { + switch (usedDep.kind) { + case R3TemplateDependencyKind.Directive: + cyclesFromDirectives.set(usedDep, cycle); + break; + case R3TemplateDependencyKind.Pipe: + cyclesFromPipes.set(usedDep, cycle); + break; + } } } } @@ -740,7 +746,7 @@ export class ComponentDecoratorHandler implements // No cycle was detected. Record the imports that need to be created in the cycle detector // so that future cyclic import checks consider their production. for (const {type, importedFile} of declarations) { - this._recordSyntheticImport(importedFile, type, context); + this.maybeRecordSyntheticImport(importedFile, type, context); } // Check whether the dependencies arrays in ɵcmp need to be wrapped in a closure. @@ -932,7 +938,7 @@ export class ComponentDecoratorHandler implements return this.cycleAnalyzer.wouldCreateCycle(origin, imported); } - private _recordSyntheticImport( + private maybeRecordSyntheticImport( importedFile: ImportedFile, expr: Expression, origin: ts.SourceFile): void { const imported = resolveImportedFile(this.moduleResolver, importedFile, expr, origin); if (imported === null) { diff --git a/packages/compiler-cli/test/ngtsc/standalone_spec.ts b/packages/compiler-cli/test/ngtsc/standalone_spec.ts index 9c5b743db3c..b87e42c3baa 100644 --- a/packages/compiler-cli/test/ngtsc/standalone_spec.ts +++ b/packages/compiler-cli/test/ngtsc/standalone_spec.ts @@ -93,6 +93,44 @@ runInEachFileSystem(() => { expect(dtsCode).toContain('i0.ɵɵPipeDeclaration'); }); + it('should compile a standalone component even in the presence of cycles', () => { + env.write('dep.ts', ` + import {Directive, Input} from '@angular/core'; + + // This import creates a cycle, since 'test.ts' imports 'dir.ts'. + import {TestType} from './test'; + + @Directive({ + standalone: true, + selector: '[dir]', + }) + export class TestDir { + @Input() value?: TestType; + } + `); + env.write('test.ts', ` + import {Component} from '@angular/core'; + import {TestDir} from './dep'; + + export interface TestType { + value: string; + } + + @Component({ + standalone: true, + imports: [TestDir], + selector: 'test-cmp', + template: '
', + }) + export class TestCmp {} + `); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('dependencies: [i1.TestDir]'); + }); + it('should error when a non-standalone component tries to use imports', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core';