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.
TABLE OF CONTENTS
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.
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Testing Services with Http in Angular 2
Want to learn how to test services in Angular 2 that have an Http dependency? Read more here!
Two-way Data Binding in Angular 2
Two-way data binding was one of the main selling points of Angular. In Angular 2, we can build directives that...
Resolving route data in Angular 2
We often want to make sure that certain data is available before a component is instantiated via routing. In this...
Angular 2 Animations - Foundation Concepts
Animation in Angular 2 is now easy and more intuitive... Learn foundational animation concepts and start animating your Angular 2...
Angular 2 is out - Get started here
Yes. The day has come. Angular 2 is finally released and here's how to get started.
Bypassing Providers in Angular 2
Dependencies are provided from the nearest ancestor provider in the injector tree. This article shows how to bypass it.