avatarHeloise Bahadiroglu

Summary

Angular's forwardRef is a mechanism to reference services or classes that have not yet been defined in the code, ensuring proper dependency injection.

Abstract

Angular's forwardRef addresses the issue of referencing services that are not yet defined in the code, which is a common scenario when services depend on each other. This is particularly relevant in cases where multiple classes or services are defined in the same file, and one service needs to inject another service that is defined later in the file. The forwardRef function defers the resolution of the service to runtime, allowing the Angular Dependency Injection system to properly instantiate services in the correct order. This is essential to prevent initialization errors that occur due to JavaScript's hoisting behavior, where variable declarations are moved to the top of their scope before code execution. While forwardRef can be avoided by structuring code to define each service in its own file, there are real-world scenarios, such as implementing custom form controls for Angular Reactive Forms, where forwardRef is necessary to reference the component within its own decorator.

Opinions

  • The author suggests that forwardRef solves a problem that developers might not immediately anticipate, highlighting its importance in specific Angular scenarios.
  • Moving services to the top of the file is presented as a straightforward solution to the problem, but it is noted that forwardRef is a more elegant approach in certain contexts.
  • The article implies that understanding JavaScript hoisting and execution context is crucial for developers to grasp why forwardRef is necessary in Angular.
  • The author points out that with the advent of Angular 9, there are fewer restrictions on using forwardRef with services provided in the root, indicating an evolution in Angular's Dependency Injection system.
  • The article emphasizes that while forwardRef can often be avoided, it remains a vital tool in the Angular framework for specific use cases, such as creating custom ControlValueAccessor for Angular Reactive Forms.

Angular: what is forwardRef and how does it work?

Photo by Steinar Engeland on Unsplash

Angular is a complete development framework and, as such, offers solutions to most problems you can think of. And probably even to some you would not have thought of. The problem forwardRef solves is probably one of these.

Unrealistic but simple example

Having a component requesting some data from a service is a pretty common Angular situation. It is also not rare that this service itself requires the help of another service. In a classic Angular project, the component and the services would be in separate files. But nothing forbids to have more than one class in a file, so we could do something like this:

As you can see, the service Test1Service is defined first, but injects Test2Service that is declared second. This doesn’t lead to any linting nor compilation error, and you can start your application with ng serve .

Opening your application in a browser though, you don’t see the expected “Hello” but an error in the console instead:

Uncaught ReferenceError: Cannot access 'Test2Service' before initialization

Apparently, using Test2Servicein Test1Service while Test2Service is physically defined under Test1Service does cause a problem.

Why does it compile? How come Angular apparently knows about the service but hasn’t initialised it?

The error doesn’t tell us that Test2Service isn’t defined, it clearly says it hasn’t been initialised yet. It is because Test2Service has been hoisted. This error is the typical error you receive when trying to use classes or variables defined with let or const before they are initialised. The JavaScript engine has scanned the script before execution and created the execution context. The variables and classes already exist, but aren’t initialised. When creating Test1Service, Test2Service has still not been initialised, but we know it exists. Hence this error. You can check this article for a quick explanation of hoisting and of the case of ES6 variables and classes.

We could now move Test2Service to the top of the file and that would fix the problem. But as we are in an unrealistic example, we are going to use forwardRef.

forwardRef, as the Angular documentation says, allows you to refer to references that are not yet defined. It is a function that takes a function as parameter, which itself returns the class we want to refer to, in this case Test2Service . When we try to create an instance of Test1Service , Angular will resolve Test2Service and will be able to create the instance.

Something worth noting: with Angular 8 (or lower for all I know), Test1Service shouldn’t be providedIn: 'root' but must be provided in AppModule , otherwise we have an error. This is not the case anymore with Angular 9.

How does it work?

Technically, the problem comes from the Angular Dependency Injection. In a basic Vanilla JS file, you could do something like this without a problem:

TestClass1 uses TestClass2 physically before TestClass2 is defined, but as we instantiate TestClass1 at the end of the script, by the miracle of closures, we see 1 in the console. Similarly, if we were to renounce injecting Test2Service in Test1Service but were simply instantiating manually, that would work:

This is because at the moment the Test1Service is actually instantiated, at runtime, Test2Service has already been defined.

With forwardRef we differ the resolution of the token (the classname) to runtime. The forwardRef function itself doesn’t do much:

It takes the function passed as a parameter (our () => Test2Service ), sets a property __forward_ref__ and returns the function. It is almost an identity function, just marks the function as needing to be “ref forwarded”.

When creating an instance of Test1Service , Angular will try to resolve the dependencies. If one parameter is a function marked with __forward_ref__ , it just calls it:

Only then is an instance created and added to the provider, which makes all the difference.

Real life example

forwardRef comes as a last resort. It usually can be avoided by creating one file for each service. There are still some cases where it can’t be avoided, otherwise it wouldn’t exist. For example, if you want to create your own form control for Angular Reactive Form, you need to provide a ControlValueAccessor to your component, which is… itself. Referencing the component in its decorator directly would lead to the same kind of error as in our example, so you need to use forwardRef :

This is one among other examples, to show that it is being used in real life cases.

A note from JavaScript In Plain English

We have launched three new publications! Show some love for our new publications by following them: AI in Plain English, UX in Plain English, Python in Plain English — thank you and keep learning!

We are also always interested in helping to promote quality content. If you have an article that you would like to submit to any of our publications, send us an email at [email protected] with your Medium username and we will get you added as a writer. Also let us know which publication/s you want to be added to.

JavaScript
Angular
Programming
Coding
Web Development
Recommended from ReadMedium