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 b775f1949b3..7f608c592a2 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts @@ -26,7 +26,7 @@ import {TypeCheckContext} from '../../../typecheck/api'; import {ExtendedTemplateChecker} from '../../../typecheck/extended/api'; import {getSourceFile} from '../../../util/src/typescript'; import {Xi18nContext} from '../../../xi18n'; -import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, getDirectiveDiagnostics, getProviderDiagnostics, isExpressionForwardReference, readBaseClass, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, wrapFunctionExpressionsInParens} from '../../common'; +import {compileDeclareFactory, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, forwardRefResolver, getDirectiveDiagnostics, getProviderDiagnostics, isExpressionForwardReference, readBaseClass, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, wrapFunctionExpressionsInParens} from '../../common'; import {extractDirectiveMetadata, parseFieldArrayValue} from '../../directive'; import {NgModuleSymbol} from '../../ng_module'; @@ -267,7 +267,7 @@ export class ComponentDecoratorHandler implements isPoisoned = true; } else if (component.has('imports')) { const expr = component.get('imports')!; - const imported = this.evaluator.evaluate(expr); + const imported = this.evaluator.evaluate(expr, forwardRefResolver); const {imports: flattened, diagnostics: importDiagnostics} = validateAndFlattenComponentImports(imported, expr); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/GOLDEN_PARTIAL.js index b6031bf7c86..1d06bb3359c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/GOLDEN_PARTIAL.js @@ -341,3 +341,47 @@ export declare class Module { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: forward_ref.js + ****************************************************************************************************/ +import { Component, forwardRef } from '@angular/core'; +import * as i0 from "@angular/core"; +export class TestComponent { +} +TestComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); +TestComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestComponent, isStandalone: true, selector: "test", ngImport: i0, template: '', isInline: true, dependencies: [{ kind: "component", type: i0.forwardRef(function () { return StandaloneComponent; }), selector: "other-standalone" }] }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComponent, decorators: [{ + type: Component, + args: [{ + selector: 'test', + standalone: true, + imports: [forwardRef(() => StandaloneComponent)], + template: '', + }] + }] }); +export class StandaloneComponent { +} +StandaloneComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: StandaloneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); +StandaloneComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: StandaloneComponent, isStandalone: true, selector: "other-standalone", ngImport: i0, template: '', isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: StandaloneComponent, decorators: [{ + type: Component, + args: [{ + selector: 'other-standalone', + standalone: true, + template: '', + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: forward_ref.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class TestComponent { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} +export declare class StandaloneComponent { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/TEST_CASES.json index d995b6c535d..c7dac587dbb 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/TEST_CASES.json @@ -84,6 +84,20 @@ ] } ] + }, + { + "description": "should handle a forwardRef in the imports of a standalone component", + "inputFiles": [ + "forward_ref.ts" + ], + "expectations": [ + { + "failureMessage": "Invalid component definition", + "files": [ + "forward_ref.js" + ] + } + ] } ] } \ No newline at end of file diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.js new file mode 100644 index 00000000000..c9d1decce37 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.js @@ -0,0 +1,19 @@ +TestComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ + type: TestComponent, + selectors: [ + ["test"] + ], + standalone: true, + features: [i0.ɵɵStandaloneFeature], + decls: 1, + vars: 0, + template: function TestComponent_Template(rf, ctx) { + if (rf & 1) { + i0.ɵɵelement(0, "other-standalone"); + } + }, + dependencies: function () { + return [StandaloneComponent]; + }, + encapsulation: 2 +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.ts b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.ts new file mode 100644 index 00000000000..6df3e64eafd --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/standalone/forward_ref.ts @@ -0,0 +1,18 @@ +import {Component, forwardRef} from '@angular/core'; + +@Component({ + selector: 'test', + standalone: true, + imports: [forwardRef(() => StandaloneComponent)], + template: '', +}) +export class TestComponent { +} + +@Component({ + selector: 'other-standalone', + standalone: true, + template: '', +}) +export class StandaloneComponent { +} diff --git a/packages/compiler-cli/test/ngtsc/standalone_spec.ts b/packages/compiler-cli/test/ngtsc/standalone_spec.ts index 9a50ff8cc83..9c5b743db3c 100644 --- a/packages/compiler-cli/test/ngtsc/standalone_spec.ts +++ b/packages/compiler-cli/test/ngtsc/standalone_spec.ts @@ -29,13 +29,13 @@ runInEachFileSystem(() => { it('should compile a basic standalone component', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core'; - + @Directive({ selector: '[dir]', standalone: true, }) export class TestDir {} - + @Component({ selector: 'test-cmp', template: '
', @@ -59,7 +59,7 @@ runInEachFileSystem(() => { it('should compile a recursive standalone component', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core'; - + @Component({ selector: 'test-cmp', template: '', @@ -96,13 +96,13 @@ runInEachFileSystem(() => { it('should error when a non-standalone component tries to use imports', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core'; - + @Directive({ selector: '[dir]', standalone: true, }) export class TestDir {} - + @Component({ selector: 'test-cmp', template: '
', @@ -119,7 +119,7 @@ runInEachFileSystem(() => { it('should compile a standalone component with schema support', () => { env.write('test.ts', ` import {Component, NO_ERRORS_SCHEMA} from '@angular/core'; - + @Component({ selector: 'test-cmp', standalone: true, @@ -147,7 +147,7 @@ runInEachFileSystem(() => { it('should error when a non-standalone component tries to use schemas', () => { env.write('test.ts', ` import {Component, NO_ERRORS_SCHEMA} from '@angular/core'; - + @Component({ selector: 'test-cmp', template: '
', @@ -166,18 +166,18 @@ runInEachFileSystem(() => { it('should compile a standalone component that imports an NgModule', () => { env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; - + @Directive({ selector: '[dir]', }) export class TestDir {} - + @NgModule({ declarations: [TestDir], exports: [TestDir], }) export class TestModule {} - + @Component({ selector: 'test-cmp', template: '
', @@ -193,15 +193,15 @@ runInEachFileSystem(() => { it('should allow nested arrays in standalone component imports', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core'; - + @Directive({ selector: '[dir]', standalone: true, }) export class TestDir {} - + export const DIRECTIVES = [TestDir]; - + @Component({ selector: 'test-cmp', template: '
', @@ -217,15 +217,15 @@ runInEachFileSystem(() => { it('should deduplicate standalone component imports', () => { env.write('test.ts', ` import {Component, Directive} from '@angular/core'; - + @Directive({ selector: '[dir]', standalone: true, }) export class TestDir {} - + export const DIRECTIVES = [TestDir]; - + @Component({ selector: 'test-cmp', template: '
', @@ -241,18 +241,18 @@ runInEachFileSystem(() => { it('should error when a standalone component imports a non-standalone entity', () => { env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; - + @Directive({ selector: '[dir]', }) export class TestDir {} - + @NgModule({ declarations: [TestDir], exports: [TestDir], }) export class TestModule {} - + @Component({ selector: 'test-cmp', template: '
', @@ -278,17 +278,17 @@ runInEachFileSystem(() => { () => { env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; - + @Directive({ selector: '[dir]', }) export class TestDir {} - + @NgModule({ declarations: [TestDir], }) export class TestModule {} - + @Component({ selector: 'test-cmp', template: '
', @@ -379,20 +379,45 @@ runInEachFileSystem(() => { expect(diags.length).toBe(1); expect(diags[0].messageText).toContain('imports'); }); + + it('should handle a forwardRef used inside `imports`', () => { + env.write('test.ts', ` + import {Component, forwardRef} from '@angular/core'; + + @Component({ + selector: 'test', + standalone: true, + imports: [forwardRef(() => StandaloneComponent)], + template: "" + }) + class TestComponent { + } + + @Component({selector: 'other-standalone', standalone: true, template: ""}) + class StandaloneComponent { + } + `); + const diags = env.driveDiagnostics(); + const jsCode = env.getContents('test.js'); + + expect(diags.length).toBe(0); + expect(jsCode).toContain('standalone: true'); + expect(jsCode).toContain('dependencies: function () { return [StandaloneComponent]; }'); + }); }); describe('NgModule-side', () => { it('should not allow a standalone component to be declared in an NgModule', () => { env.write('test.ts', ` import {Component, NgModule} from '@angular/core'; - + @Component({ selector: 'test-cmp', template: 'Test', standalone: true, }) export class TestCmp {} - + @NgModule({ declarations: [TestCmp], }) @@ -408,13 +433,13 @@ runInEachFileSystem(() => { it('should not allow a standalone pipe to be declared in an NgModule', () => { env.write('test.ts', ` import {Pipe, NgModule} from '@angular/core'; - + @Pipe({ name: 'test', standalone: true, }) export class TestPipe {} - + @NgModule({ declarations: [TestPipe], }) @@ -430,20 +455,20 @@ runInEachFileSystem(() => { it('should allow a standalone component to be imported by an NgModule', () => { env.write('test.ts', ` import {Component, NgModule} from '@angular/core'; - + @Component({ selector: 'st-cmp', standalone: true, template: 'Test', }) export class StandaloneCmp {} - + @Component({ selector: 'test-cmp', template: '', }) export class TestCmp {} - + @NgModule({ declarations: [TestCmp], imports: [StandaloneCmp], @@ -457,19 +482,19 @@ runInEachFileSystem(() => { it('should allow a standalone directive to be imported by an NgModule', () => { env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; - + @Directive({ selector: '[st-dir]', standalone: true, }) export class StandaloneDir {} - + @Component({ selector: 'test-cmp', template: '
', }) export class TestCmp {} - + @NgModule({ declarations: [TestCmp], imports: [StandaloneDir], @@ -483,7 +508,7 @@ runInEachFileSystem(() => { it('should allow a standalone pipe to be imported by an NgModule', () => { env.write('test.ts', ` import {Component, Pipe, NgModule} from '@angular/core'; - + @Pipe({ name: 'stpipe', standalone: true, @@ -493,7 +518,7 @@ runInEachFileSystem(() => { return value; } } - + @Component({ selector: 'test-cmp', template: '{{data | stpipe}}', @@ -501,7 +526,7 @@ runInEachFileSystem(() => { export class TestCmp { data = 'test'; } - + @NgModule({ declarations: [TestCmp], imports: [StandalonePipe], @@ -539,12 +564,12 @@ runInEachFileSystem(() => { it('should error when a non-standalone entity is imported into an NgModule', () => { env.write('test.ts', ` import {Component, Directive, NgModule} from '@angular/core'; - + @Directive({ selector: '[dir]', }) export class TestDir {} - + @NgModule({ imports: [TestDir], }) @@ -562,7 +587,7 @@ runInEachFileSystem(() => { it('should compile a basic standalone directive', () => { env.write('test.ts', ` import {Directive} from '@angular/core'; - + @Directive({ selector: '[dir]', standalone: true, @@ -576,7 +601,7 @@ runInEachFileSystem(() => { it('should compile a basic standalone pipe', () => { env.write('test.ts', ` import {Pipe} from '@angular/core'; - + @Pipe({ name: 'testpipe', standalone: true,