How do Angular and NestJS manage to dynamically resolve injection tokens during runtime using the TypeScript type hints provided at compile time?

Frameworks such as Angular and NestJS in TypeScript utilize dependency injection by converting TypeScript type hints into injection tokens. These tokens are then used to fetch dependencies and inject them into constructors at runtime:

@Injectable() // <-- registers this class in the container
class X {

}

class Y {
  constructor(private x: X) // <-- this is detected to need class X
  { 

  } 
}

Even though TypeScript type annotations get compiled into JavaScript, where private x: X simply becomes x, how do these frameworks inspect the types of constructor arguments during runtime? It is likely that either the TypeScript compiler provides some information for them, or these frameworks perform source code analysis during the build process.

Can someone provide insight into how this process is typically executed in TypeScript?

Answer №1

Exploring a new nest new project and examining the AppController

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

(While using @Controller(), it applies similarly to @Injectable(), at least for the context of this discussion)

Now analyzing its compiled output, dist/app.controller.js

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppController = void 0;
const common_1 = require("@nestjs/common");
const app_service_1 = require("./app.service");
let AppController = class AppController {
    constructor(appService) {
        this.appService = appService;
    }
    getHello() {
        return this.appService.getHello();
    }
};
__decorate([
    (0, common_1.Get)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", String)
], AppController.prototype, "getHello", null);
AppController = __decorate([
    (0, common_1.Controller)(),
    __metadata("design:paramtypes", [app_service_1.AppService])
], AppController);
exports.AppController = AppController;
//# sourceMappingURL=app.controller.js.map

A lot happening at the top of the file, defining methods like __deccorate. Let's see it in action with the @Controller() decorator

AppController = __decorate([
    (0, common_1.Controller)(),
    __metadata("design:paramtypes", [app_service_1.AppService])
], AppController);

The "design:paramtypes' is crucial for Nest during application start as it specifies what to inject where. Nest identifies AppService based on this information, then recursively reads the metadata of all related components until it can create the service, adding the provider to a metadata map and ensuring it doesn't duplicate services within the module context. It follows the metadata and injection tree to instantiate each service.

Note: The types for the @Get() decorator and parameters are visible here. This data is utilized by Nest, particularly in pipes.


An important note is that the __decorate() method and related metadata won't be included in Typescript output unless there is at least one decorator on the class, hence the use of @Inejctable(). If the class lacks injections, technically @Injectable() isn't necessary, but conventionally kept for consistency.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The variable 'data' is not a property of the type 'any[]'

I am currently facing an issue with a dummy service I created to fetch dummy data. When calling this service from a component ts file, I encountered the following error. After searching through some similar posts, I still haven't been able to resolve ...

Ways to obtain a tab and designate it as the default in angular when using angular material Tabs

I am facing an issue with accessing tabs within a nested component. The parent component contains the tab feature and to reach the tabs inside the child component, I am using the following code: document.querySelectorAll('.mat-tab-group'); The a ...

Is it possible to remove a complete row in Angular 2 using Material Design

JSON [ { position: 1, name: 'test', value: 1.0079, symbol: 'HHH' }, { position: 2, name: 'test2', value: 4.0026, symbol: 'BBB' }, { position: 3, name: 'test3', value: 6.941, symbol: 'BB' }, ...

Does adding a callback to object.ngOnDestroy() replace its internal onDestroy() method?

I'm enhancing the drag and drop feature in Angular <div cdkDropList appImprovedDropList> <div cdkDrag>item 1</div> <div cdkDrag>item 2</div> <div cdkDrag>item 3</div> </div> The directive appImpro ...

Classbased Typescript implementation for managing state with a Vuex store

Hey everyone, I'm currently working on a Vue project with Vuex using decorators for strong typing in my template. As someone new to the concept of stores, I am struggling to understand how to properly configure my store to work as expected in my comp ...

Encountering a syntax error when attempting to utilize the colon symbol for specifying data types

Currently, I am a novice who is delving into the world of TypeScript. Here is a snippet of code that I have written: let num: number = 123; console.log(123); However, when attempting to run this file using Node.js and saving it as test.ts, I encounter the ...

Issue: Keeping the mat-form-field element in a single line

I am facing an issue with incorporating multiple filters for each column in an angular material table. I cannot figure out why the filter input is not moving to a new line under the header. Here is an image for reference -> Angular material table with m ...

At first, the Angular disabled property does not seem to be functioning as

Trying to toggle a button's disabled state based on whether an array is empty or not in an Angular application. The button implementation looks like this: <button (click)="doStuff()" [disabled]="myObject.myArray.length === 0"> ...

Typescript causing undefined React Router match issue

Currently, I am working on a basic eCommerce Proof of Concept using react and TypeScript. Unfortunately, I am facing an issue where I am unable to pass props to a product detail page or access the match containing the params. This is how my Routes pages a ...

Encountering a problem with the installation of angular-cli

When running ng serve on my Mac, I get the error -bash: ng: command not found. It seems like I need to install angular-cli. However, when I try sudo npm install -g angular-cli, I encounter the following issues. Does anyone have any idea what might be wron ...

Is there a way for me to display my custom text status before toggling the button on mat-slide-toggle?

Upon loading my page, the toggle button is visible but lacks any text until toggled. Upon clicking the toggle button, it displays "on", but subsequently fails to switch back when clicked again, staying stuck on "on" until clicked once more to correctly di ...

Currently, I am working on integrating the ngx-bootstrap datepicker feature into my project

I'm currently working on integrating the ngx-bootstrap datepicker and would like to display the months as Jan, Feb, Mar, Apr instead of January, February. How can I achieve this customization? ...

What is the best way to implement an Angular Guard that utilizes an API service for validation and redirects in case of failure?

Hello there! I am currently working on an Angular 7 application that deals with time cards. One of the main features I have implemented is a CanActivate Guard for controlling access to certain components. The CanActivate code utilizes Observables to decid ...

Navigating through diverse objects in Typescript

My challenge involves a state object and an update object that will merge with the state object. However, if the update value is null, it should be deleted instead of just combining them using {...a, ...b}. const obj = { other: new Date(), num: 5, ...

Encountering a NativeScript error while attempting to set up and execute the android platform

When I try to run the command "tns build android", I encounter the following issue: The task ':asbg:generateInterfaceNamesList' failed to execute. An error occurred: Could not retrieve property 'jarFiles' for project ':asbg&apo ...

The sum is being treated as a concatenation instead of an addition in this case

Why is the somma value showing the concatenation of totaleEnergetico and totaleStrutturale instead of a sum? RiepilogoCombinatoStComponent.ts export class RiepilogoCombinatoStComponent implements OnInit { constructor() { } interventi: AssociazioneI ...

Issue with sending functions to other components in Angular

I'm currently facing an issue with passing functions to other objects in Angular. Specifically, I've developed a function generateTile(coords) that fills a tile to be used by leaflet. This function is located within a method in the MapComponent. ...

Analyzing a string using an alternative character

I want to convert the string "451:45" into a proper number. The desired output is 451.45. Any help would be appreciated! ...

How can I effectively retrieve a value from Ionic storage and use it as an authorization token instead of receiving the promise "Bearer [object Promise]"?

In my Ionic 4 angular project, the storage.get('token').then() function returns a promise object instead of the refresh token. For JWT authentication, I send the access token as the bearer token in the authorization headers using an HTTP interce ...

NPM Alert: Outdated lockfile detected The package-lock.json file appears to have been generated using a previous version of NPM

package-lock.json causing issues when upgrading from Angular 9 to 10 Is there a way to generate a new package-lock.json file and successfully upgrade Angular 9 to 10? I'm looking to update from Angular 9 to 10 and troubleshoot the error related to p ...