@Self or @Optional @Host? The visual guide to Angular DI decorators.
Most of the time, when we need a service injected into our Angular classes, we can rely on the default service lookup that the framework provides. But that’s only, well, a default — and Angular also provides several other interesting possibilities. This article aims to collect all the options in a concise form.
The default
Let’s look at a sample KidComponent
. It needs to know about toys that are being kept in a ToysService
, so we inject that into its constructor
(usually with a private
modifier, here omitted for brevity).
By default Angular will first check if the component defines a dependency injector in its decorator. If it does (1.), the component (specifically: each of its instances) will receive its own instance of the service. If it doesn’t find in on the component, it will look for a parent injector (e.g. the parent component (2.), its ancestors etc), up the injectors tree and in the end it will stop on the application-wide instance of the service defined on a one of our NgModule
s. Unless it’s not even there, in which case we will get a “No provider
” error.
@Self()
If we decorate the parameter with @Self()
, it’s like there was only the first step of the previously discussed default behaviour. The only place allowed to find the injector is the component itself (3.). If it isn’t defined there…
@Optional()
… well, that would be an error, right? Yes, definitely (4.), but also not necessarily: if your component doesn’t absolutely need that service, you can decorate a parameter with the @Optional()
decorator and in such case of no provider found, no error will occur. Instead Angular will set the value for our service tonull
(5.)
You can set @Optional()
in any other DI scenario discussed here as well.
If you’re following, in our Angular code, this could actually happen:
constructor(@Optional() @Self() private readonly where: DidWeGoWrong){
.
I guess with great power comes great responsibility ¯\_(ツ)_/¯
@SkipSelf()
At this point, @SkipSelf()
decorator should be, forgive the bad pun, self-explanatory. The behaviour is like the default — we’re looking up the injectors hierarchy, but this time skipping the first step of looking for a possible injector in the requesting component (6.)
@Host()
@Host()
decorator makes Angular to look for the injector on the component itself, so in that regard it may look similar to the @Self()
decorator (7.). But that’s actually not the end: if the injector is not found there, it looks for the injector up to its host component.
Wait, what?
There are two common scenarios where said host component is something different than our current class.
- We’ve been looking at a
Component
as our example, but we may just as well have aDirective
here instead. In that case it can be used on aComponent
that defines its injector and that component would be the directive’s host. - Or we can have our
KidComponent
projected intoParentComponent
(by that<ng-content></ng-content>
thingy). Then we also say that our component is being hosted byParentComponent
— and ifParentComponent
providesToyService
andKidComponent
does not, the@Host()
decorator of that inner component would still get that service’s instance (8.)
Edit: there is a great in-depth write-up on the @Host()
decorator by Max Wizard K that I highly recommend: https://blog.angularindepth.com/a-curios-case-of-the-host-decorator-and-element-injectors-in-angular-582562abcf0a.
Now I know what you are all eager to ask: can we have constructor (@Host() @SkipSelf() @Optional() toys: ToysService) {...}
?
The answer is: Yup, we can.
Practical note:
The post was quite theoretical (hopefully still useful) and I am working on a more hands-on article with some “oh-my-god-that’s-so-true!” examples, but in the meantime if you want to see these options in action I highly recommend this talk on forms https://youtu.be/CD_t3m2WMM8?t=1881 (timestamped to where the advanced DI stuff begins) given by Kara Erickson at the recent AngularConnect.