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

I'm looking for a way to modify the Turkish characters and spaces in the names of JSON data objects. I plan to do this using WebApi

I am facing an issue with fetching data through an API. The JSON data format contains Turkish characters and spaces, causing problems when trying to display the data in a datatable. I have attempted to use the replace and parse functions, but so far, I hav ...

Exploring the depths of Angular8: Utilizing formControlName with complex nested

After dedicating numerous hours to tackle this issue, I finally came up with a solution for my formGroup setup: this.frameworkForm = this.formBuilder.group({ id: [null], name: ['', Validators.required], active: [true], pa ...

How to Apply a CSS Class to the Body Tag in Angular 2.x

How can I add [class.fixed]="isFixed" to the body tag when Angular 2.x is bootstrapped inside the body (outside my-app)? <html> <head> </head> <body [class.fixed]="isFixed"> <my-app>Loading...</my-app> </body> & ...

My previously functioning TypeScript code suddenly ceased to work after I ran the yarn install command

Everything was running smoothly with my TypeScript code, both locally and on the server. However, after restarting the production code, I encountered errors (which required me to reinstall packages with yarn install). Strangely enough, when I try to yarn i ...

Unable to establish breakpoints in TypeScript within VS Code

I seem to be facing an issue while trying to set breakpoints in my TypeScript nodejs app using Visual Studio Code. Despite following the guidelines provided on the Visual Studio Code website, I have not been able to achieve success. Below is the content o ...

Having trouble bringing my custom-built Angular module into my Angular application

Currently considering the utilization of this Yeoman generator as a starting point for a small project that will contain several reusable form components to be published. The generator constructs a module and an example component, directive, pipe, and serv ...

What is the process for exporting libraries in TypeScript?

Encountering an error when attempting to export socket.io using this method import socketIOClient from 'socket.io-client';. The error message is TS1192: Module '"***/node_modules/socket.io-client/build/index"' has no default e ...

Error: Cannot locate 'import-resolver-typescript/lib' in jsconfig.json file

Issue: An error occurred stating that the file '/Users/nish7/Documents/Code/WebDev/HOS/frontend/node_modules/eslint-import-resolver-typescript/lib' could not be found. This error is present in the program because of the specified root file for c ...

Issue encountered during Angular 2 upgrade from RC4 to RC5: Error message stating that at least one component needs to be bootstrapped

Trying to upgrade to version 2.0.0-rc.5 with Router at 3.0.0-rc.1. After following the upgrade instructions from the documentation (transitioning from RC4 which is currently working fine), I encountered this error, but I am unsure what's causing it: ...

Sourcemaps experiencing major issues when using TypeScript and the browserify/gulp combination

Despite successfully generating sourcemaps from my build process using browserify with gulp, I encountered issues when trying to debug. Breakpoints would often jump to different lines in Chrome, indicating that the script was not pausing where it should. A ...

Angular removing every query string parameters

Linked to but distinct from: How to maintain query string parameters in URL when accessing a route of an Angular 2 app? I am facing an issue with my basic Angular application where adding a query parameter results in its removal, both from the browser&apo ...

Create a TypeScript class object with specified constructor arguments

I've been working on a function that is supposed to execute the init method of a class and then return an instance of that class. However, I'm running into issues with maintaining the constructor and class types. This is what I have tried so far ...

Differences between Angular2 local template variables and Jade ID shortcutsIn Angular2, local

Angular2 has introduced the local template variable feature, which is created using #var. When using the Jade Template Engine, this syntax gets converted to #var="var". Is there a method to avoid this conversion? Otherwise, accessing the original local t ...

Is there a way to easily access the last element of an array in an Angular2 template without the need to iterate through the entire

I'm not trying to figure out how to access looping variables like i, first, last. Instead, my question is about how to retrieve and set variables as template variables. My current approach doesn't seem to be working... <div #lastElement="arr ...

Handling HTTP Client Errors in Angular 4.x

I am currently working on developing an application that utilizes Angular for the front end and Laravel 5.0 for the back end. One of the challenges I am facing is handling errors in HTTP requests. Here is an example from my MenuController.php, where I pr ...

Enhance the appearance of a custom checkbox component in Angular

I developed a customized toggle switch for my application and integrated it into various sections. Recently, I decided to rework it as a component. However, I am encountering an issue where the toggle switch button does not update in the view (it remains t ...

Every row within the data grid component must contain a distinct `id` property

How do I utilize statId instead of id in my code? Should I create a unique id property for each row? Error: MUI: All rows in the data grid component must have a distinct id property. Alternatively, you can define a custom id using the getRowId prop. A ...

What is the most effective method for integrating Bootstrap CSS into an Angular project?

When incorporating a Bootstrap CSS file into an Angular project that has already been added using yarn add <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f2909d9d868186809382b2c6dcc3dcc3">[email protected]</a>, th ...

Troubleshooting History.push issue in a Typescript and React project

Currently, I'm tackling a project using React and TypeScript, but I've encountered a problem. Whenever I attempt to execute a history.push function, it throws an error that reads: Uncaught (in promise) TypeError: history.push is not a function. ...

Invoke a function within a component, within that very component

Hey there, I've got an Angular 2 component with a @HostListener. My goal is to call a method from this component using the @HostListener. Check out the code snippet below for my attempt at implementing this: The Component import { Component, Host ...