what does do forwardRef in angular?

unos baghaii picture unos baghaii · Jun 17, 2018 · Viewed 9.9k times · Source

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!' }

Answer

Armando Perez picture Armando Perez · Jun 17, 2018

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:

enter image description here

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. :)