Skip to content

Commit 1d871c0

Browse files
crisbetoalxhub
authored andcommitted
fix(compiler): forward referenced dependencies not identified as deferrable (angular#52017)
Fixes that we weren't accounting for dependencies using `forwardRef` when determining if they can be lazy-loaded. Fixes angular#52014. PR Close angular#52017
1 parent cc7973f commit 1d871c0

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {TypeCheckableDirectiveMeta, TypeCheckContext} from '../../../typecheck/a
2626
import {ExtendedTemplateChecker} from '../../../typecheck/extended/api';
2727
import {getSourceFile} from '../../../util/src/typescript';
2828
import {Xi18nContext} from '../../../xi18n';
29-
import {combineResolvers, compileDeclareFactory, compileInputTransformFields, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, forwardRefResolver, getDirectiveDiagnostics, getProviderDiagnostics, InjectableClassRegistry, isExpressionForwardReference, readBaseClass, ReferencesRegistry, removeIdentifierReferences, resolveEncapsulationEnumValueLocally, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, validateHostDirectives, wrapFunctionExpressionsInParens,} from '../../common';
29+
import {combineResolvers, compileDeclareFactory, compileInputTransformFields, compileNgFactoryDefField, compileResults, extractClassMetadata, extractSchemas, findAngularDecorator, forwardRefResolver, getDirectiveDiagnostics, getProviderDiagnostics, InjectableClassRegistry, isExpressionForwardReference, readBaseClass, ReferencesRegistry, removeIdentifierReferences, resolveEncapsulationEnumValueLocally, resolveEnumValue, resolveImportedFile, resolveLiteral, resolveProvidersRequiringFactory, ResourceLoader, toFactoryMetadata, tryUnwrapForwardRef, validateHostDirectives, wrapFunctionExpressionsInParens,} from '../../common';
3030
import {extractDirectiveMetadata, parseDirectiveStyles} from '../../directive';
3131
import {createModuleWithProvidersResolver, NgModuleSymbol} from '../../ng_module';
3232

@@ -1186,7 +1186,9 @@ export class ComponentDecoratorHandler implements
11861186
// for defer loading.
11871187
if (analysisData.meta.isStandalone && analysisData.rawImports !== null &&
11881188
ts.isArrayLiteralExpression(analysisData.rawImports)) {
1189-
for (const node of analysisData.rawImports.elements) {
1189+
for (const element of analysisData.rawImports.elements) {
1190+
const node = tryUnwrapForwardRef(element, this.reflector) || element;
1191+
11901192
if (!ts.isIdentifier(node)) {
11911193
// Can't defer-load non-literal references.
11921194
continue;

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9010,6 +9010,47 @@ function allTests(os: string) {
90109010
'import("./cmp-a").then(m => m.CmpB)]');
90119011
expect(jsContents).not.toContain('import { CmpA, CmpB }');
90129012
});
9013+
9014+
it('should lazy-load dependency referenced with a fowrardRef', () => {
9015+
env.write('cmp-a.ts', `
9016+
import { Component } from '@angular/core';
9017+
9018+
@Component({
9019+
standalone: true,
9020+
selector: 'cmp-a',
9021+
template: 'CmpA!'
9022+
})
9023+
export class CmpA {}
9024+
`);
9025+
9026+
env.write('/test.ts', `
9027+
import { Component, forwardRef } from '@angular/core';
9028+
import { CmpA } from './cmp-a';
9029+
9030+
@Component({
9031+
selector: 'test-cmp',
9032+
standalone: true,
9033+
imports: [forwardRef(() => CmpA)],
9034+
template: \`
9035+
@defer {
9036+
<cmp-a />
9037+
}
9038+
\`,
9039+
})
9040+
export class TestCmp {}
9041+
`);
9042+
9043+
env.driveMain();
9044+
9045+
const jsContents = env.getContents('test.js');
9046+
9047+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9048+
expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]');
9049+
9050+
// The `CmpA` symbol wasn't referenced elsewhere, so it can be defer-loaded
9051+
// via dynamic imports and an original import can be removed.
9052+
expect(jsContents).not.toContain('import { CmpA }');
9053+
});
90139054
});
90149055

90159056
describe('setClassMetadataAsync', () => {

0 commit comments

Comments
 (0)