Skip to content

fix(defer): lazy-load an imports entry written as forwardRef(() => X) - #640

Open
edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/defer-lazy-forwardref
Open

edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/defer-lazy-forwardref

Conversation

@edbzn

@edbzn edbzn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #632.

Problem

A standalone component listed as imports: [forwardRef(() => CmpA)] and used only inside @defer stayed eager: the implicit-deferral gate from #313 required every imports entry to be a bare identifier, and the identifier list it matched against skipped forwardRef(...) entries. The static import { CmpA } stayed and ɵɵdefer(1, 0) got no dependency function. ngtsc resolves the entry through forwardRefResolver (tryUnwrapForwardRef in packages/compiler-cli/src/ngtsc/annotations/common/src/util.ts) before resolveAllDeferredDependencies matches selectors.

Fix

  • decorator.rs: record the classes written as forwardRef(() => X) in the imports array (ComponentMetadata::forward_ref_imports), using the existing try_unwrap_forward_ref, so only @angular/core's forwardRef counts. imports itself is unchanged (partial/Direct emit still reads it).
  • transform.rs: import_entry_class maps each raw imports entry to its class (X or a resolved forwardRef(() => X), arrow or return body). The gate, the candidate list and the removal from the eager ɵɵgetComponentDepsFactory array all use it.

Output now matches ngc: ɵɵdefer(1, 0, () => [import("./lazy").then(m => m.LazyCmp)]), ɵɵgetComponentDepsFactory(Parent, []), and no static import.

Selectorless deps (selectorless_spec.ts: "should defer selectorless symbols") are out of scope: OXC rejects selectorless components outright (pipeline/ingest.rs), so there is nothing to defer yet.

Checks

  • New in tests/defer_implicit_imports_test.rs: forward_ref_import_used_only_inside_defer_is_lazy_loaded (port of defer_spec.ts "should lazy-load dependency referenced with a fowrardRef", arrow and block-body forms) and forward_ref_not_from_angular_core_stays_eager.
  • cargo test -p oxc_angular_compiler --all-features: 3518 passed, 0 failed.
  • cargo fmt; cargo clippy -p oxc_angular_compiler --all-features adds no warnings on the changed lines.

🤖 Generated with Claude Code

Resolve `@angular/core`'s `forwardRef` in `imports` before the implicit
`@defer` deferral, like ngtsc's `forwardRefResolver`, so a component used
only inside `@defer` is loaded with a dynamic `import()`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@defer doesn't lazy-load an imports entry written as forwardRef(() => X)

1 participant