what does do forwardRef in angular?
Asked Answered
V

2

26

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!' }
Viki answered 17/6, 2018 at 7:24 Comment(0)
E
21

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 the enclose function was created, it captured the variable reference. So when later the variable was updated to the 5 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.

Expansionism answered 17/6, 2018 at 8:38 Comment(4)
@Spangen.How resolveForwardRef works if i pass seevice.i have tried but it returns undefiend for class service.thanksAlphanumeric
@scott, please can you create a new question?Expansionism
#62643136Alphanumeric
@spangen.created new questionAlphanumeric
A
30

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

Aimee answered 17/6, 2018 at 9:29 Comment(6)
thanks for the example! that really help me understand forwardRef. So what if we defined Service2Service earlier in our source code? Then we wouldn't need to use forwardRef right? But like you said it provides more flexibility in organizing our code.Jenette
@RyanEfendy a little bit late my answer but i would not recommend to use forwardRef in order to have the ability of "flexibility in organizing"! It will tend to write unorganized code because of "flexibility in organizing". Services, Components, Directives, etc there places should are in dedicated folders. We should always import what we want.Dorran
@RyanEfendy I found something which looks weird but make sense, see: github.com/angular/material2/blob/… (yes, sometimes you will organize code in same file. Here make it sense for me but this a other topic. Just want to show a example which make sense)Dorran
Hey, thanks for the example. Even though, I believe that sometimes things need to be organized in a certain way as shown in your example above. I agree that we should import things. Nice addition to the answer. :)Aimee
@ArmandoPerez if you would use @Service() on the Service2 the framework will do it by it self. This will hoist the service2 and will handle the dependency injection. But your explanation is really good! Thank u. it reminded me the whole HOISTING stuff ( we all forget if we not using )Singularize
Thanks @IamStalker, great that you added valuable information to the post!Aimee
E
21

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 the enclose function was created, it captured the variable reference. So when later the variable was updated to the 5 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.

Expansionism answered 17/6, 2018 at 8:38 Comment(4)
@Spangen.How resolveForwardRef works if i pass seevice.i have tried but it returns undefiend for class service.thanksAlphanumeric
@scott, please can you create a new question?Expansionism
#62643136Alphanumeric
@spangen.created new questionAlphanumeric

© 2022 - 2024 — McMap. All rights reserved.