22

Angular dependency injection let you inject a string, function, or object using a token instead of a service class.

I declare it in my module like this:

providers: [{ provide: MyValueToken, useValue: 'my title value'}]

and I use it like this:

constructor(@Inject(MyValueToken) my_value: string) {
  this.title = my_value;
}

However, how can I update the value from the component and let other components get every time the new value? in other words, I want to simulate the functionality of using something like a BehaviorSubject to emit and receive values.

If this is not possible then what is the use of those injection tokens values if they provide only static data as instead I can simply declare the static value in my component and use it directly.

Hamed Baatour
  • 6,664
  • 3
  • 35
  • 47
  • 1
    Yes, you could declare that static value, and if it's only used in one place you *should*. DI is useful when it's used in multiple places and you want consistency and when you want to inject alternative values for testing. If neither is the case, don't add the complexity - just because you can, doesn't mean you should. I'd recommend reading https://angular.io/guide/dependency-injection-in-action. – jonrsharpe Oct 15 '17 at 13:11
  • @jonsharpe Great answer just the point! thank you :) – Hamed Baatour Oct 15 '17 at 13:12

4 Answers4

26

Instead of a primitive which is immutable, you can use a BehaviorSubject, then access and update it in one component and subscribe in the other:

export const MY_VALUE_TOKEN = new InjectionToken<BehaviorSubject<string>>('my.Value.token')
providers: [{ provide: MY_VALUE_TOKEN, useValue: new BehaviorSubject('')}]
    
// consumer
constructor(@Inject(MY_VALUE_TOKEN) my_value: BehaviorSubject<string>) {
  my_value.subscribe((my_value)=>this.title = my_value);
}

// producer
constructor(@Inject(MY_VALUE_TOKEN) my_value: BehaviorSubject<string>) {
  my_value.next('my title value');
}
Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • How does this differ from declaring a service class and using a BehaviorSubject with it. I feel that all this angular dependency injection token value thing is useless ceremony in a way that adds complexity for nothing. so what is a great use of those injection tokens? – Hamed Baatour Oct 15 '17 at 12:58
  • 1
    @HamedBaatour The same reasons why DI pattern is beneficial. Extensibility and testability. – Estus Flask Oct 15 '17 at 13:01
  • 1
    @HamedBaatour, see [this](http://misko.hevery.com/2008/11/11/clean-code-talks-dependency-injection/) – Max Koretskyi Oct 15 '17 at 13:03
  • so it's a best practice to avoid declaring any static values directly in the component and it's better to inject them all using DI? – Hamed Baatour Oct 15 '17 at 13:09
  • 1
    @HamedBaatour It always depends. If there is more than one component that depends on this value, the value should clearly be defined outside of the component. If it's static it can be a constant. If it changes, it should be contained within a service, like in this example. – Estus Flask Oct 15 '17 at 13:12
  • @estus got it! correct me if I am wrong: In conclusion, DI tokens are used for sharing static values across many components as it's better for testing. if the value needs to be dynamic a service needs to be created. if the value is static and it is used by one component I should declare it inside that component only. right? – Hamed Baatour Oct 15 '17 at 13:18
  • so, in other words, the answer to my question is not a best use case. as in the case of using a `BehaviorSubject` it is better to use a full-service class without introducing an extra complexity. – Hamed Baatour Oct 15 '17 at 13:21
  • 2
    @HamedBaatour Yes. But if a value that is used by several components doesn't have a chance to be changed, you can safely skip DI part and make it `const`. Again, it always depends. `window` never changes but it makes sense to keep it as a provider for testing reasons, like [that](https://stackoverflow.com/a/45926615/3731501). – Estus Flask Oct 15 '17 at 13:26
  • 1
    @estus thank you so much for clarifying all of this. thumbs up! – Hamed Baatour Oct 15 '17 at 13:32
  • better to have `my_value.asObservable().subscribe((my_value)=>this.title = my_value);` – Vincent Feb 26 '23 at 09:27
9

In addition to the Wizard:

If you have a use-case where every consumer needs its own instance of BehaviourSubject. (I happen to be in this use-case). Make sure you define a factory.

const myFactory = () => { return new BehaviorSubject<string>('') };

providers: [
    { provide: MyValueToken, useFactory: myFactory }
]

// Then, as proposed in the top-answer.

// consumer
constructor(@Inject(MyValueToken) my_value: BehaviorSubject) {
  my_value.subscribe((my_value)=>this.title = my_value);
}

// producer
constructor(@Inject(MyValueToken) my_value: BehaviorSubject) {
  my_value.next('my title value');
}
Andre Elrico
  • 10,956
  • 6
  • 50
  • 69
  • This makes sure that every consumer gets its own BehaviourSubject, right? Otherwise emitting a new value in a producer would trigger an update in all the other consumers too. – user130685 Mar 09 '20 at 13:31
  • Every component or module with a common ancestor that 'provides' this token will share the same instance of it. If you put `{ provide: MyValueToken, useFactory: myFactory }` on two components they wouldn't be able to 'talk to each other' but if you declared it on a common ancestor component or module then they would. – Simon_Weaver Jun 30 '21 at 02:35
6

If you don't want to use a BehaviorSubject, you could provide a simple class with a getter and setter instead.

class MyValue {

  get value(): string {
    return this._value;   
  }

  set value(val: string) {
   this._value = val;
  }
  private _value = '';

}

const MY_VALUE_TOKEN = new InjectionToken<MyValue>('MY_VALUE_TOKEN ');

// Provide class in either module or component providers array.
providers: [
  { provide: MY_VALUE_TOKEN , useClass: MyValue },
]

class MyComponent {

  // Inject in component constructor
  constructor(
    @Inject(MY_VALUE_TOKEN) private _myValue: MyValue,
  ) {

    // Access current value
    console.log(this._myValue.value);

    // Set new value
    this._myValue.value = 'new value';
  }

}

Luke Gatchell
  • 81
  • 1
  • 3
  • 2
    Wont this only update the "local" copy of the injected variable? I believe OP is asking how to apply the change globally – Sajjan Sarkar Nov 21 '19 at 14:28
  • @SajjanSarkar This IS effectively a global variable either at the level of the module or component. You could inject this in two places and they could 'talk to each other'. It wouldn't be a local copy each time unless you added it to the providers of every component. – Simon_Weaver Jun 30 '21 at 02:31
  • However as it stands it isn't very useful because there's no consideration for change detection - your updated value may not display right away (depends what made the change). That's why the observable options probably make sense because you can then handle the change, but don't forget to unsubscribe or use async pipe. If you're able to set an Input value property on a component that's preferable – Simon_Weaver Jun 30 '21 at 02:32
  • 1
    @Simon_Weaver I completely agree. If you need to react to changes, using an observable is definitely the better approach. – Luke Gatchell Jul 01 '21 at 12:13
  • This answer exactly matched my needs. Thanks a lot! – Simon Borsky Jan 27 '23 at 06:59
1

Regarding WHY...

Since you were also wondering WHY you might want to inject a static structure, Angular Material does this quite often to provide configuration to components.

eg. for Chips control:

@NgModule({
  providers: [
    {
      provide: MAT_CHIPS_DEFAULT_OPTIONS,
      useValue: {
        separatorKeyCodes: [ENTER, COMMA]
      }
    }
  ]
})

No need to understand what this data represents, just realize that you're injecting a token MAT_CHIPS_DEFAULT_OPTIONS with value { separatorKeyCodes: [ENTER, COMMA] }

This will be inherited from your AppModule, or from whichever module or component you define it - just like any other injectable service would. This could equally be in your @Component or @Directive definition to provide config just for a single component (and its children).

Of course when a different module, or component in your application needs different configuration you inject it there and only the child components will inherit it.

To be honest it may seem a pain having to do this, instead of just setting a value on a component, but that's the way it works for a lot of Angular Material controls. And of course the benefit is you do it only once and everything inherits it.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689