@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).

The default injection — just enough for 99% cases™

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 NgModules. 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 a Directive here instead. In that case it can be used on a Component that defines its injector and that component would be the directive’s host.
  • Or we can have our KidComponent projected into ParentComponent(by that <ng-content></ng-content> thingy). Then we also say that our component is being hosted by ParentComponent — and if ParentComponent provides ToyService and KidComponent does not, the @Host() decorator of that inner component would still get that service’s instance (8.)

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.

Did you learn something new? If so, please click the clap 👏 button below ⬇️ so more people can see this!