Skip to content

fix(core): preserve generic type parameters when injecting or querying tokens - #71762

Draft
JeanMeche wants to merge 1 commit into
angular:mainfrom
JeanMeche:fix/53894-inject-generic-type
Draft

JeanMeche wants to merge 1 commit into
angular:mainfrom
JeanMeche:fix/53894-inject-generic-type

Conversation

@JeanMeche

@JeanMeche JeanMeche commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

In TypeScript, instantiation expressions on class constructors specialize construct signatures (new), leaving the prototype property unspecialized. Because ProviderToken<T> includes AbstractType<T> ({ prototype: T }), passing specialized tokens to inject() or signal queries caused TypeScript to infer T from prototype, widening the return type.

To preserve type parameters:

  • Added inject<T = any>(token: abstract new (...args: any[]) => T): T; overload.
  • Added abstract new (...args: any[]) => ReadT and InjectionToken<ReadT> overloads for query read options.
  • Added abstract new (...args: any[]) => LocatorT overloads for query locators.

Fixes #53894
Fixes #48126

@angular-robot angular-robot Bot added area: core Issues related to the framework runtime requires: TGP This PR requires a passing TGP before merging is allowed labels Nov 2, 2026
@ngbot ngbot Bot added this to the Backlog milestone Nov 2, 2026
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch 4 times, most recently from 765149b to 169e53d Compare November 2, 2026 01:52
@JeanMeche JeanMeche added the state: blocked on G3 cleanup This change requires a G3 cleanup label Nov 2, 2026
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch 12 times, most recently from 547fe92 to de1fc86 Compare November 2, 2026 10:53
@JeanMeche

JeanMeche commented Nov 2, 2026 •

Copy link
Copy Markdown
Member Author

Type Inference Summary

Using abstract new (...args: any[]) => T (and InjectionToken<ReadT> for queries read option) allows instantiation expressions to preserve their type arguments without complex conditional types:

Call Before After
inject(GenericService) GenericService<any> GenericService<'a' | 'b'>
inject(GenericService<'a'>) GenericService<any> GenericService<'a'>
inject(AbstractGenericService<'x'>) AbstractGenericService<any> AbstractGenericService<'x'>
inject(AbstractDateAdapter<Date>) AbstractDateAdapter<any, any> AbstractDateAdapter<Date, any>
inject(TemplateRef<MyCtx>) TemplateRef<any> TemplateRef<MyCtx>
viewChild(MyDir<string>) Signal<MyDir<any> | undefined> Signal<MyDir<string> | undefined>
viewChild('ref', {read: ElementRef}) Signal<ElementRef<any> | undefined> Signal<ElementRef<any> | undefined>
viewChild('ref', {read: ElementRef<HTMLAnchorElement>}) Signal<ElementRef<any> | undefined> Signal<ElementRef<HTMLAnchorElement> | undefined>
viewChild('tpl', {read: TemplateRef<MyCtx>}) Signal<TemplateRef<any> | undefined> Signal<TemplateRef<MyCtx> | undefined>

JeanMeche added a commit to JeanMeche/angular that referenced this pull request Nov 2, 2026
…ecting or querying tokens

Ports the test changes from angular#71762.
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch from de1fc86 to 469230d Compare November 2, 2026 20:10
@SkyZeroZx

SkyZeroZx commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

As a question, could this be considered a breaking change? or at least I think we might need a migration script after this is merged, since I've seen code like this many times in codebases

 private readonly dataService: DataService<MyItem> = inject(DataService);

@JeanMeche

JeanMeche commented Nov 2, 2026 •

Copy link
Copy Markdown
Member Author

It's definitely a breaking change. It's a large breakage inside Google3.

If we agree to land it. It will probably be a 2-stepper. (the 2nd step being the actual breaking change during the v23 window + a migration script alongside it).

@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch from 469230d to 789887a Compare November 3, 2026 17:43
@JeanMeche JeanMeche removed the requires: TGP This PR requires a passing TGP before merging is allowed label Nov 3, 2026
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch 2 times, most recently from e772785 to 6de5e46 Compare November 3, 2026 18:14
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch 5 times, most recently from c7beaf5 to 454585c Compare November 3, 2026 22:06
…g tokens

In TypeScript, instantiation expressions on class constructors specialize construct signatures (`new`), leaving the `prototype` property unspecialized. Because `ProviderToken<T>` includes `AbstractType<T>` (`{ prototype: T }`), passing specialized tokens to `inject()` or signal queries caused TypeScript to infer `T` from `prototype`, widening the return type.

To preserve type parameters:
- Added `inject<T = any>(token: abstract new (...args: any[]) => T): T;` overload.
- Added `abstract new (...args: any[]) => ReadT` and `InjectionToken<ReadT>` overloads for query `read` options.
- Added `abstract new (...args: any[]) => LocatorT` overloads for query locators.

Fixes angular#53894
Fixes angular#48126
@JeanMeche
JeanMeche force-pushed the fix/53894-inject-generic-type branch from 454585c to 704d1bd Compare November 3, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Issues related to the framework runtime state: blocked on G3 cleanup This change requires a G3 cleanup

Projects

None yet

2 participants