38

I am running into 'opaque tokens' as a solution to implementing global constants in Angular 2, for example here: Define global constants in Angular 2

Despite reading the docs, I can't seem to grasp the point.

Using an OpaqueToken is preferable to using strings as tokens because of possible collisions caused by multiple providers using the same string as two different tokens.

What? What's an Angular2 token to begin with? All I get on google are answers on JSON Web Tokens (their role in auth, etc, etc), which I understand, but are obviously not related in any way.

What's an Opaque Token? What is it used for?

P.S. More docs on opaque tokens as used to provide constants. They didn't help me very much, however.

Community
  • 1
  • 1
VSO
  • 11,546
  • 25
  • 99
  • 187
  • Why didn't that help? That's exactly what they're for. – jonrsharpe Dec 22 '16 at 18:07
  • I don't get what a 'token' is in this context, at all. – VSO Dec 22 '16 at 18:08
  • *"Input elements other than white space and comments... reserved words, identifiers, literals, and punctuators..."* - https://ariya.io/2012/07/most-popular-javascript-tokens. Here's a good example of use of an `OpaqueToken`; injecting the `LOCALE_ID`: http://stackoverflow.com/a/39344889/3001761 – jonrsharpe Dec 22 '16 at 18:09
  • Yep, more or less. If they'd just done `export const LOCALE_ID = 'localeId';` or something, there'd be no way for the DI to distinguish it from the same constant elsewhere. – jonrsharpe Dec 22 '16 at 18:13
  • 3
    Think of 'token' as of a synonym to 'identifier'. And `OpaqueToken` is A2-specific alternative to `Symbol`. If you know what `Symbol` is for, you already know the deal. – Estus Flask Dec 22 '16 at 19:51
  • Is there a practical reason for this A2 specific alternative, or do you get the same result by exporting a plain old ES6 Symbol? (Trying it now...) – Tim Consolazio Dec 27 '16 at 17:10
  • 1
    I don't see a reason not to just import a constants.ts file and be done tbh. – VSO Dec 27 '16 at 17:50
  • @VSO Genuinely curious -- was there a reason you felt Gunter's answer wasn't "check worthy"? Anything you felt was missing? – ruffin Mar 13 '20 at 16:47
  • 1
    @ruffin To be honest, I don't remember now. I will come back to review it. Usually I do check answers though, so I am guessing there was a reason. P.S. Gunter is an awesome expert, so I am sure he is correct. I will come back to this. – VSO Mar 13 '20 at 17:35

4 Answers4

68

update Angular4

In Angular4 OpaqueToken is deprecated and will be replaced by InjectionToken. InjectionToken allows to pass a generic type parameter.

export let APP_CONFIG = new InjectionToken<MyConfig>("app.config");

See also

original

What? What's an Angular2 token to begin with?

What's an Opaque Token? What is it used for?

A token is a key for providers of Angulars dependency injection. Providers are registered with a key and components, directives, and service classes instantiated by DI get dependencies injected which are looked up by provider keys.

DI supports types, strings, OpaqueToken and objects as keys.

export let APP_CONFIG = new OpaqueToken("app.config");

export let APP_CONFIG_2 = {};

providers: [
  MyService, // type is key and value
  {provide: MyService, useClass: MyFancyServiceImpl}, // type is key, `MyFancyServiceImpl` is the value (or rather the information how to create the value
  {provide: 'myservice', useClass: MyService}, // key is a string
  {provide: APP_CONFIG, useValue: {a: 'a', b: 'b'}} // key is an `OpaqueToken`
  {provide: APP_CONFIG_2, useValue: {a: 'a', b: 'b'}} // key is an object

]
// one of these decorators needs to be added to make DI work
@Injectable()
@Component()
@Directive()
@Pipe()
class MyComponent {
  // DI looks up a provider registered with the key `MyService` 
  constructor(private myService: MyService) {} 

  // Same as before but explicit
  constructor(@Inject(MyService) private myService: MyService) {} 

  // DI looks up a provider registered with the key 'myService'
  constructor(@Inject('myservice') private myService: MyService) {} 

  // DI looks up a provider registered with the `OpaqueKey` `APP_CONFIG`
  constructor(@Inject(APP_CONFIG) private myConfig: any) {} 

  // DI looks up a provider registered with the object `APP_CONFIG_2`
  constructor(@Inject(APP_CONFIG_2) private myConfig: any) {} 

The object key (APP_CONFIG_2) and the OpaqueToken (APP_CONFIG) need to be the exact same instance. A different instance with the same content won't work. This makes it easy to look up where the key is declared and whether the provider and the injection target use the same key.

For a string it can be a different instance, this brings the risk, that the same string value is used in different modules and might cause conflicts or the wrong provider being injected.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 4
    you are always the first with most detailed answer! :) – Stepan Suvorov Jan 22 '17 at 12:04
  • @StepanSuvorov I try as far as my time allows. Glad to hear you think it's a good answer :) – Günter Zöchbauer Jan 22 '17 at 12:07
  • 3
    @GünterZöchbauer Not sure if I am wasting your time with saying thanks, but I appreciate it! – VSO Jan 23 '17 at 14:49
  • Perhaps this sounds dumb, but whats the difference between this and saving the 'global constant' in a file which will be included all over my app? Is it to making testing easier by utilizing DI? Or are there any other good reasons for it. – realappie Oct 31 '17 at 10:22
  • This article (or maybe finally REALLY needing to use this outside of tests helped me too): https://medium.com/@michelestieven/angular-writing-configurable-modules-69e6ea23ea42 – VSO Oct 18 '19 at 14:26
  • @GünterZöchbauer how about Angualr 8 – user123456 Nov 18 '19 at 16:29
4

What's an Opaque Token? What is it used for?

Opaque Token used for inject another provider(service) with same name.

This allows you to avoid naming collisions.

     const MY_HTTP_TOKEN: OpaqueToken = new OpaqueToken('Http');

     providers: [
        { provide: MY_HTTP_TOKEN, useClass: Http }
     ]

     constructor(@Inject(MY_HTTP_TOKEN) private myHttpService){}
Vitaliy.Makogon
  • 354
  • 3
  • 15
  • This is a pretty useful reply now that I am looking at it. Helps explain why you would even bother. – VSO Oct 18 '19 at 14:27
4

An Opaque Token is just a runtime class that is used as a unique identifier for injector providers.

Lets say you have a value, 'secretId' that you want to use into a few services and components. You don't want to hard code this in your services and components, as it will change in the future. As well you want to write tests for your services and components.

With a class you can use the class as an implementation, type and injector token. For a string value, other literal expressions, objects, etc -- you have nothing to use as an injector token. You could use a string as an injector token, but there is no garuntee that string will be unique. With Opaque tokens, even if two tokens have the same name, they will be evaluated as different tokens.

Martin
  • 15,820
  • 4
  • 47
  • 56
2

I'm new to angular 2, but I want to try to interpret what I understood from https://blog.thoughtram.io/angular/2016/05/23/opaque-tokens-in-angular-2.html in a simple code. CMIIW.

...
const CONFIG_ONE = {title: 'My awesome app'};
let configOneToken = 'config';  //'config' is an example of string-type token

const CONFIG_TWO = {title: 'My fantastic app'};
let configTwoToken = 'config';

providers = [
  { provide: configOneToken, useValue: CONFIG_ONE },
  { provide: configTwoToken, useValue: CONFIG_TWO }
];

That piece of code will have an issue (the later will override the former) because it has conflict ('config' == 'config'). It may be too obvious and meaningless in that toy code, but in a real code, we may not be able to identify this conflict easily when one of the provider is defined in a third party library. So, to fix that, we can use OpaqueToken as in the following code.

...
const CONFIG_ONE = {title: 'My awesome app'};
const OPAQUE_TOKEN_ONE = new OpaqueToken('config'); //this is an example of object-type token

const CONFIG_TWO = {title: 'My fantastic app'};
const OPAQUE_TOKEN_TWO = new OpaqueToken('config');

providers = [
  { provide: OPAQUE_TOKEN_ONE, useValue: CONFIG_ONE },
  { provide: OPAQUE_TOKEN_TWO, useValue: CONFIG_TWO }
];

Two instances of any same class are never be equal (new OpaqueToken('config') != new OpaqueToken('config')) and therefor avoiding conflict. The OpaqueToken itself is nothing but just as a simple doing-nothing class.

elfan
  • 1,131
  • 6
  • 11