mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Dylan Hunn
parent
10691c626b
commit
32c625d027
@@ -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);
|
||||
|
||||
|
||||
+44
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+19
@@ -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
|
||||
});
|
||||
+18
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user