1

tl;dr: How does Angular know that an injection token passed to injector.get corresponds with an injection token of a particular provider?


According to the Angular documentation, an InjectionToken can be used to inject interfaces. The example given is the following:

const myInterface = injector.get(new InjectionToken<MyInterface>('SomeToken'));

This would give me an instance that has been provided somewhere else. In particular, somewhere, some piece of code has to provide an implementation of MyInterface using a corresponding injection token.

My question is: How does Angular determine that the above injection token passed to injector.get corresponds with whatever is supplied in the provider?

It appears to be a part of new InjectionToken<MyInterface>('SomeToken'), so let's look at the options:


MyInterface is out, because interfaces are erased during TypeScript compilation. The InjectionToken docs say so themselves:

Use an InjectionToken whenever the type you are injecting is not reified (does not have a runtime representation) such as when injecting an interface (...)


It's not the 'SomeToken' string, either, because, as the docs state:

Description for the token, used only for debugging purposes, it should but does not need to be unique


This leaves us with the InjectionToken instance. So, does Angular check whether the InjectionToken instance passed to injector.get is the same as the one in the provider? It does not seem so, because in the above example, the instance is newly created - the provider cannot possibly have a reference to the same instance.


So, how does Angular know which provider to use for a given injection token?

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
F-H
  • 663
  • 1
  • 10
  • 21
  • 1
    This is quite bizarre indeed. I'm inclined to think that the documentation at angular.io is just wrong. The source code, [specified here](https://github.com/angular/angular/blob/16.1.8/packages/core/src/di/injection_token.ts#L13-L99), does nothing special at all. It's also worth noting that the docs for injection token are poorly written. There is no such thing as a reified type and angular just perpetuates confusion by conflating types and values. – Aluan Haddad Aug 04 '23 at 07:50
  • 1
    @AluanHaddad: Oh-oh, it looks like you are right: While I did try and understand the injector source code in Angular (and did not really succeed, given that it appears to be written in quite a complicated way), I have just verified empirically that creating a new injection token instance upon retrieval (even with exactly the same type and settings as in the provider) leads to a `NullInjectorError`. This gives me some serious headaches, as I need to reuse the same injection token with different generic type arguments. I thought I could create the appropriately typed token in a factory ... – F-H Aug 04 '23 at 11:22
  • ... function called with the generic argument both on the provider and the injector side, but now this won't work. – F-H Aug 04 '23 at 11:22
  • 1
    @AluanHaddad: I have [reported the issue](https://github.com/angular/angular/issues/51270) on the Angular documentation issue tracker. – F-H Aug 04 '23 at 11:37
  • Good on you for reporting the issue. If I understand correctly, you could In the meantime, you still could use a generic factory function to create your injection tokens. As the type information is erased, what will matter is the actual token under which the dependency was registered. You could use a cache. This would definitely break from idiomatic angular use however. – Aluan Haddad Aug 04 '23 at 11:46
  • 1
    @AluanHaddad: Well, I *can* use a "factory" that stores the untyped token instances, and returns them from a function that takes a generic parameter and then casts the tokens accordingly. Not so concerned about idiomatic Angular use, but I had hoped that I could avoid such typecast shenanigans while using TypeScript. – F-H Aug 04 '23 at 11:57
  • I hear you, but whenever you explicitly provide a generic type argument to a function because it doesn't correspond to any value argument of that function, it's suspect except in rare cases where you have a framework requirement. In other words, the type injected by the token in your example isn't verified. I would create my tokens by providing factories, classes, etc. Which would obviate the need for type assertions. Again I probably am not seeing clearly just how you're intending to use this. – Aluan Haddad Aug 04 '23 at 12:15
  • @AluanHaddad: While I think I have found a somewhat satisfactory solution for myself now, I have [described my case more in detail in a separate question](https://stackoverflow.com/questions/76836271/how-do-i-transfer-generically-typed-data-using-dependency-injection). As this is the very first time I am working with TypeScript and Angular, there may well be something I am missing. – F-H Aug 04 '23 at 13:14
  • That question explains your goal well but you won't find Angular framework support I fear. Remember that all types are fully erased so there's really only ever one runtime value that corresponds to any generic or overloaded function, method, or class. That means that whatever you implement has to work regardless of the types which exist to be checked by the compiler to provide static validation, intellisense, and documentation. You should differentiate based on _values_ such as string literals, which provide a basis to distinguish runtime behavior and from which types can in turn be extracted. – Aluan Haddad Aug 04 '23 at 14:39
  • @AluanHaddad: Well, things work despite the erased types. But the point was, there doesn't seem to be a way around explicitly specifying the generic type parameter on the function call (or doing the cast oneself, which is, IMHO, even uglier). – F-H Aug 04 '23 at 15:34
  • Yes. I was saying the opposite. I'm saying you could write `new InjectionToken('description')` and it would still work. There is no correlation. – Aluan Haddad Aug 04 '23 at 15:53
  • @AluanHaddad: Of course, at some point, casting must happen, because - as you say - when injection token identity is checked, the type is not checked. But that casting can be hidden away in our own framework, so at least developers at the higher levels of our application do not have to deal with it. – F-H Aug 05 '23 at 18:03
  • I agree that's preferable. Thinking about your problem more generally, I would be inclined to use something like a part of the URL as a key to correlate with the type at runtime. – Aluan Haddad Aug 07 '23 at 13:07
  • 1
    @AluanHaddad That's not really applicable in my project (any given URL/page can display many different widgets for different business objects at a time), but it may be a useful approach in some cases. – F-H Aug 14 '23 at 13:21

0 Answers0