What does forwardRef do in angular, and what is its usage?
here is an example:
import {Component, Injectable, forwardRef} from '@angular/core';
export class ClassCL { value; }
@Component({
selector: 'my-app',
template: '<h1>{{ text }}</h1>',
providers: [{provide: ClassCL, useClass: forwardRef(() => ForwardRefS)}]
})
export class AppComponent {
text;
constructor( myClass: ClassCL ) {
this.text = myClass.value;
}
}
Injectable()
export class ForwardRefS { value = 'forwardRef works!' }
According to Angular's documentation:
Allows to refer to references which are not yet defined.
I believe that in order to better understand how forwardRef works, we need to understand how things happen under the Javascript's hood. I will provide an example of a specific case in which you may need to use forwardRef, but take into account that other different cases may arise.
As we may know, Javascript functions are hoisted to the top of its execution contexts. Functions are objects themselves and other objects may also be created from functions. Because functions allow programmers to create object instances, ECMAScript 2015 created some sort of syntactic sugar in order to make Javascript to feel a little more closer to class based languages, like Java. Enter the class:
class SomeClassName { }
If we go into a Javascript compiler (in my case I am using Babel) and paste this, the result would be:
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var SomeClassName = function SomeClassName() {
_classCallCheck(this, SomeClassName);
};
The most interesting part is to notice that our class in reality is a function in the background. Like functions, variables are also hoisted within its execution context. The only difference is that while we can call functions (because we can reference its pointer even though it was hoisted), variables are hoisted and given a default value of undefined. The variable is assigned a value, probably other than undefined, at runtime at the given line of the assignment. For instance:
console.log(name);
var name = 'John Snow';
Actually becomes:
var name = undefined;
console.log(name) // which prints undefined
name = 'John Snow';
Ok, with all this in mind, let's now jump into Angular. Let's say we have the following code in our app:
import { Component, Inject, forwardRef, Injectable } from '@angular/core';
@Injectable()
export class Service1Service {
constructor(private service2Service: Service2Service) {
}
getSomeStringValue(): string {
return this.service2Service.getSomeStringValue();
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private service1Service: Service1Service) {
console.log(this.service1Service.getSomeStringValue());
}
}
export class Service2Service {
getSomeStringValue(): string {
return 'Some string value.';
}
}
And of course, we need to provide these services. Let's provide them in our AppModule:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent, Service1Service, Service2Service } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [Service1Service, Service2Service],
bootstrap: [AppComponent]
})
export class AppModule { }
The important line in our AppModule's metadata is:
providers: [Service1Service, Service2Service]
If we run this code, we'll get the following error:
Hmmmm, interesting... What's going on here? Well, based on the explanation given before, Service2Service becomes a function in the background, but this function gets assigned into a variable. This variable is hoisted, but the value of it is undefined. Because of all this, the parameter cannot be resolved.
Enter forwardRef
In order to solve this issue we have the beautiful function named forwardRef. What this function does is that it takes a function as a parameter (in the example I show I use an arrow function). This function returns a class. forwardRef waits until Service2Service is declared, and then it triggers the arrow function being passed. This results in returning the class that we need in order to create the Service2Service instance. So, your app.component.ts code would look like this:
import { Component, Inject, forwardRef, Injectable } from '@angular/core';
@Injectable()
export class Service1Service {
constructor(@Inject(forwardRef(() => Service2Service)) private service2Service) {
}
getSomeStringValue(): string {
return this.service2Service.getSomeStringValue();
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private service1Service: Service1Service) {
console.log(this.service1Service.getSomeStringValue());
}
}
export class Service2Service {
getSomeStringValue(): string {
return 'Some string value.';
}
}
In conclusion, based on the example I have provided, forwardRef allows us to reference types that are defined later on in our source code, preventing our code from crashing and providing more flexibility in the way we organize things in our code.
I really hope my answer serves you well. :)
From Angular's API docs on forwardRef
:
Allows to refer to references which are not yet defined.
For instance,
forwardRef
is used when the token which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when the token which we use when creating a query is not yet defined.
There is a good write up at Angular In Depth.
Here's an extract:
Why does forwardRef work?
Now the question may pop up in your head how the
forwardRef
works. It actually has to do with how closures in JavaScript work. When you capture a variable inside a closure function it captures the variable reference, not the variable value. Here is the small example to demonstrate that:let a; function enclose() { console.log(a); } enclose(); // undefined a = 5; enclose(); // 5
You can see that although the variable
a
was undefined at the moment theenclose
function was created, it captured the variable reference. So when later the variable was updated to the5
it logged the correct value.And
forwardRef
is just a function that captures a class reference into closure and class becomes defined before the function is executed. Angular compiler uses the function resolveForwardRef to unwrap the token or provider type during runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With