fix(compiler-cli): handle forwardRef in imports of standalone component (#45869)

Fixes that the compiler wasn't resolving `forwardRef` values when they're used in the `imports` of a standalone component.

PR Close #45869
This commit is contained in:
Kristiyan Kostadinov
2022-05-04 11:28:26 +02:00
committed by Dylan Hunn
parent 10691c626b
commit 32c625d027
6 changed files with 161 additions and 41 deletions
@@ -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);
@@ -341,3 +341,47 @@ export declare class Module {
static ɵinj: i0.ɵɵInjectorDeclaration<Module>;
}
/****************************************************************************************************
* 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: '<other-standalone></other-standalone>', 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: '<other-standalone></other-standalone>',
}]
}] });
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<TestComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<TestComponent, "test", never, {}, {}, never, never, true>;
}
export declare class StandaloneComponent {
static ɵfac: i0.ɵɵFactoryDeclaration<StandaloneComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<StandaloneComponent, "other-standalone", never, {}, {}, never, never, true>;
}
@@ -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"
]
}
]
}
]
}
@@ -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
});
@@ -0,0 +1,18 @@
import {Component, forwardRef} from '@angular/core';
@Component({
selector: 'test',
standalone: true,
imports: [forwardRef(() => StandaloneComponent)],
template: '<other-standalone></other-standalone>',
})
export class TestComponent {
}
@Component({
selector: 'other-standalone',
standalone: true,
template: '',
})
export class StandaloneComponent {
}
@@ -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: '<div dir></div>',
@@ -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: '<test-cmp></test-cmp>',
@@ -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: '<div dir></div>',
@@ -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: '<div></div>',
@@ -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: '<div dir></div>',
@@ -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: '<div dir></div>',
@@ -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: '<div dir></div>',
@@ -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: '<div dir></div>',
@@ -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: '<div dir></div>',
@@ -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: "<other-standalone></other-standalone>"
})
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: '<st-cmp></st-cmp>',
})
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: '<div st-dir></div>',
})
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,