In our article on Dependency Injection in Angular we explored what dependency injection actually is, and how it is implemented in the Angular 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’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.
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 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 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 function 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 Master Class at Shopware
Join our upcoming public training!Get a ticket →
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
RxJS Master Class and courseware updates
If you've been following us for a while, you're quite aware that we're always striving to provide up-to-date and high-quality...
Advanced caching with RxJS
When building web applications, performance should always be a top priority. One very efficient way to optimize the performance of...
Custom Overlays with Angular's CDK - Part 2
In this follow-up post we demonstrate how to use Angular's CDK to build a custom overlay that looks and feels...
Custom Overlays with Angular's CDK
The Angular Material CDK provides us with tools to build awesome and high-quality Angular components without adopting the Material Design...
Easy Dialogs with Angular Material
Building modals and dialogs isn't easy - if we do it ourselves. Angular Material comes with a powerful dialog service...
A web animations deep dive with Angular
Angular comes with a built-in animation system that lets us create powerful animations based on the Web Animations API. In...