Skip to content

fix(di): inject constructor params typed with a namespace-qualified name - #634

Open
edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/namespaced-ctor-token
Open

edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/namespaced-ctor-token

Conversation

@edbzn

@edbzn edbzn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #633.

Problem

A constructor parameter typed with a qualified name (zero: ns.Zero, one: ns.one.One) got ɵɵinvalidFactoryDep(i), so the class threw when instantiated. Every decorator's extract_param_token dropped TSQualifiedName, even though setClassMetadata already resolves the same type to i1.one.One.

Fix

Resolve the qualified name like ngtsc's typeToValue (packages/compiler-cli/src/ngtsc/reflection/src/type_to_value.ts): the leftmost name decides.

  • import * as ns: the members are read off the emitted namespace import (i1.Zero, i1.one.One).
  • import {ns} / import {ns as alias}: they are read off the export (i1.ns.Zero).
  • A local namespace: the name is emitted as written (local.L, entityNameToValue).

@Injectable, @Directive, @Pipe and @NgModule now build the token as a member chain (util::type_name_to_value_expr), and resolve_factory_dep_namespaces resolves the chain through its root. Components keep tokens as names, so their dependency gets token_member_path and token_is_namespace_import; the partial declaration emits the token as written (token: ns.one.One).

Side effect: because resolve_factory_dep_namespaces now resolves member chains, an @Inject(ns.TOKEN) argument rooted at an import also goes through the namespace import (i1.TOKEN) instead of ns.TOKEN. Plain @Inject(TOKEN) identifiers were already handled this way. The two forms are equivalent at runtime. This also fixes @Inject(mod.T) for a named import with emit_class_metadata: false: import elision removed the mod import and left mod.T unbound.

Checks

  • New tests/namespaced_ctor_token_test.rs ports both ngtsc spec cases ("...type references to namespaced symbols using a namespace import" / "...using named imports"). It also covers a local namespace, Component/Directive/NgModule/Pipe factories, and the partial component declaration. All 5 tests fail on main and pass with the fix.
  • cargo test -p oxc_angular_compiler --all-features: 3521 passed, 0 failed, 1 ignored.
  • cargo fmt --check passes. cargo clippy -p oxc_angular_compiler --all-features reports no warnings on the changed lines.

🤖 Generated with Claude Code

A parameter typed `ns.Zero` / `ns.one.One` got `ɵɵinvalidFactoryDep`. Resolve
it like ngtsc's `typeToValue`: a namespace import's members read off the
emitted namespace (`i1.one.One`), a named import's off its export
(`i1.ns.Zero`), and a local namespace's as written.

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.

Constructor param typed ns.Foo (namespace-qualified) compiles to ɵɵinvalidFactoryDep

1 participant