In our article on Dependency Injection in Angular 2 we explored what dependency injection actually is, and how it is implemented in the Angular 2 framework. If you haven’t read that article yet, I highly recommend you doing so, since this article is based on it.
In a another article we even learned about host and visibility of dependencies as another aspect of Angular 2’s DI system. But that doesn’t mean that we’ve already discovered all features of the machinery yet. In this article we’ll take a look at forward references. Another tiny, yet useful feature of the DI system in Angular 2.
Understanding the problem
As a small recap, here we have an
AppComponent that relies on DI to get a
NameService injected. As we are using TypeScript, all we need to do is to annotate our constructor parameter
nameService with the
NameService type. This gives Angular all the relevant info to correctly resolve the dependency at runtime.
This works well, but let’s see what happens when we inline the contents of
nameService.ts directly in
app.ts. In this case, you probably wouldn’t want to do that but bear with me as I’m trying to make my point.
When we try to run this code we notice that it stopped working. In my case, I wasn’t even able to get an error reported to the console which I assume boils down to some glitch with debugging TypeScript code with source maps. Anyways, when we use the debuggers “Pause on exceptions” feature we can follow the rabbit into it’s hole somewhere deep down inside the Angular 2 framework.
Cannot resolve all parameters for AppComponent(undefined). Make sure they all have valid type or annotations.
Ok, this gives us a little hint. It seems
NameService is undefined in the constructor of
AppComponent. This makes sense if you look at the flow of the code because we already used
NameService in the constructor of
Let’s see what happens when we move
NameService to the top so that it’s declared before it’s first usage.
Classes aren’t hoisted for a good reason
extend keyword to inherit from something. In particular, when it inherits from an expression which is absolutely valid.
Consider this ES6 code:
However, try making
Animal an expression rather than a function declaration.
Again, this will be hoisted but now it becomes this.
At the point where
class Dog extends Animal is interpreted
Animal is actually undefined and we get an error. We can easily fix that by moving the
Animal expression before the declaration of
Dog just like a regular ES5 constructor function? We would end up with this code:
Dog is hoisted to the top the code breaks at the moment where the
extends Animal is interpreted because
undefined at that moment. The important thing to note here is that the
extends part has to be evaluated at the right point in time. Therefore classes aren’t hoisted.
So the class must always be declared before it’s usage?
Ok, now that we understood why classes aren’t hoisted what does that mean for our earlier Angular 2 example where we had to move the
NameService to the very top? Is this the only way to get things working?
Turns out there is a solution we can reach for. Instead of annotating our
nameService parameter with the
NameService type which we learned evaluates to
undefined at this point in time, we can use the
@Inject annotation in conjunction with the
forwardRef function as demonstrated here.
forwardRef does is, it takes a function as a parameter that returns a class. And because this functions isn’t immediately called but instead is called after
NameService is declared it is safe to return
NameService from it. In other words: At the point where
() => NameService runs
NameService isn’t undefined anymore.
The described scenario isn’t something that one has to deal with too often. This only becomes a problem when we want to have a class injected that we created in the same file. Most of the time we have one class per file and import the classes that we need at the very top of the file so we won’t actually suffer from the fact that classes aren’t hoisted.
Angular 2 Master Class in Helsinki
Learn Angular 2 in our upcoming public training!Join now
Get updates on new articles and trainings.
Join over 1000 other developers who get our content first.
Protecting Routes using Guards in Angular 2
When building applications, we often want to protect the users from entering or leaving certain areas. We could have an...
Model-driven Forms in Angular 2
Earlier this year we've talked about template-driven forms in Angular 2 and how they enable us to build sophisticated forms...
Cold vs Hot Observables
One of the most exciting topics around Angular 2 is its relationship to Observables. There's one particular area that is...
Routing in Angular 2 revisited
Routing is hard. If you've followed the development of Angular 2 the last couple of months, especially the router, you've...
Component-Relative Paths in Angular 2
Creating components in Angular 2 is awesome in so many ways. Developers should be careful, however, when using external component...
How to prevent name collisions in Angular 2 providers
Angular 2's dependency injection improved in many ways. It not only is more flexible when it comes to assembling dependencies...