Skip to content
Aleksandr Melnik edited this page Oct 27, 2024 · 11 revisions

@Module Decorator

The @Module decorator is a crucial part of the Nexus IoC library, used to define modules in your application. A module is a class annotated with a @Module decorator, and it is used to organize and group related components, services, and providers. Modules help in managing the application's structure, making it more maintainable and scalable.

Purpose

  • Organize Application Structure: Modules allow you to logically group related components, services, and providers together.
  • Dependency Injection Management: Modules help in managing and injecting dependencies across different parts of the application.
  • Modularity: Encourages a modular architecture, making the application easier to understand, develop, and maintain.

How It Works

A module is defined by decorating a class with the @Module decorator. The decorator takes an object with metadata properties that describe the module, including its providers, imports, and exports.

Properties of @NsModule Decorator

  • imports: An array of modules that are required by this module. These modules will be imported and their exported providers will be available in the current module.
  • providers: An array of providers that will be instantiated by the IoC container and can be injected into other components within the module.
  • exports: An array of providers that will be available to other modules that import this module.

Usage

Example 1: Basic Module

import { NsModule, Injectable } from '@nexus-ioc/core';

@Injectable()
class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

@NsModule({
  providers: [AppService],
})
export class AppModule {}

Example 2: Module with Dependencies

In this example, we define a module that depends on another module. This is achieved using the imports property.

import { NsModule, Injectable } from '@nexus-ioc/core';

@Injectable()
class DependencyService {
  getDependency(): string {
    return 'Dependency';
  }
}

@Module({
  providers: [DependencyService],
})
export class DependencyModule {}

@Injectable()
class AppService {
  constructor(private readonly dependencyService: DependencyService) {}

  getHello(): string {
    return `Hello ${this.dependencyService.getDependency()}`;
  }
}

@NsModule({
  imports: [DependencyModule],
  providers: [AppService],
})
export class AppModule {}

Example 3: Exporting Providers

In this example, we define a module that exports its providers to be used by other modules.

import { NsModule, Injectable } from '@nexus-ioc/core';

@Injectable()
class SharedService {
  getShared(): string {
    return 'Shared Service';
  }
}

@Module({
  providers: [SharedService],
  exports: [SharedService],
})
export class SharedModule {}

@Injectable()
class AppService {
  constructor(private readonly sharedService: SharedService) {}

  getHello(): string {
    return `Hello from ${this.sharedService.getShared()}`;
  }
}

@NsModule({
  imports: [SharedModule],
  providers: [AppService],
})
export class AppModule {}

Dependency Scope in Nexus IoC

In Nexus IoC, modules and providers have a specific scope, controlling where and how dependencies can be accessed. Dependencies are resolved by looking at a module's imports and exports, ensuring clear boundaries between different parts of the application.

How Imports and Exports Work

Modules can share their providers with other modules by using imports and exports. This system allows you to control which services are available across different modules. If a module needs a provider from another module, it must import that module. If a module wants to share a provider with other modules, it must export that provider.

Example of Imports and Exports

import { NsModule, Injectable, Inject } from '@nexus-ioc/core';

@Injectable()
class UserService {
  getUser() {
    return 'User';
  }
}

@NsModule({
  providers: [UserService],
  exports: [UserService], // UserService is made available to other modules
})
export class UserModule {}

@Injectable()
class OrderService {
  constructor(@Inject(UserService) private userService: UserService) {}

  getOrderDetails() {
    return `Order for ${this.userService.getUser()}`;
  }
}

@NsModule({
  imports: [UserModule], // UserModule is imported to access UserService
  providers: [OrderService],
})
export class OrderModule {}
  • UserModule defines and exports UserService, making it available to other modules.
  • OrderModule imports UserModule to gain access to UserService.

Closest Imports Only

When Nexus IoC tries to resolve a dependency, it looks only in the imports of the current module. If the required provider isn't found there, it won't search in other modules further up the hierarchy. This ensures that each module only has access to the providers it specifically imports, maintaining modularity and clear boundaries.

For example

@NsModule({
  imports: [OrderModule],  // AppModule doesn't import UserModule
})
export class AppModule {}

Here, AppModule imports OrderModule, but cannot access UserService because it hasn't imported UserModule. Even though UserModule is part of the application, it isn't directly available to AppModule.

Re-Exporting Providers

Nexus IoC supports re-exporting providers. This means that if a module imports another module and exports it, the providers from the imported module can be passed along to other modules without needing to import them again.

@NsModule({
  imports: [UserModule],  // Import UserModule
  exports: [UserModule],  // Re-export UserModule's providers
})
export class SharedModule {}

@Module({
  imports: [SharedModule],  // AppModule imports SharedModule and gains access to UserService
})
export class AppModule {}
  • SharedModule imports and re-exports UserModule.
  • AppModule imports SharedModule and now has access to UserService through the re-export.

Key Takeaways:

  • Imports: A module must import another module to access its providers.
  • Exports: A module must export its providers to share them with other modules.
  • Closest Imports: Dependencies are resolved only in the nearest module's imports.
  • Re-Exporting: Modules can re-export providers to simplify sharing across different parts of the application.

@Global Decorator in Nexus IoC

In Nexus IoC, the @Global decorator is used to mark a module as global, which means that its providers will be available across the entire application without needing to explicitly import the module in other modules. Once a module is marked as global, all of its exported providers can be injected anywhere in the application, regardless of the module boundaries.

Purpose of @Global

  • Simplify dependency management: By marking a module as global, you make its providers available throughout the application without repeatedly importing the same module.
  • Useful for shared services: Common services like logging, configuration, or utilities can be defined in a global module so that they are accessible from any module in the app.

Example of a Global Module

import { NsModule, Global, Injectable } from '@nexus-ioc/core';

@Injectable()
class ConfigService {
  getConfig() {
    return 'App Config';
  }
}

@Global()
@NsModule({
  providers: [ConfigService],
  exports: [ConfigService],  // Export to make the service available globally
})
export class ConfigModule {}

In this example:

  • ConfigService is provided in the ConfigModule.
  • The @Global() decorator makes ConfigModule global, so ConfigService is now available throughout the entire app without importing ConfigModule in every other module.

Key Points:

  • Global Availability: Any provider exported by a global module can be injected into any part of the application without importing the module.
  • Single Registration: You only need to register a global module once, and it will be automatically available across all other modules.
  • Typical Use Cases: Shared services like configuration, logging, or common utilities are ideal candidates for being part of a global module.

Best Practices:

  • Use the @Global decorator for services that need to be accessed across multiple modules without redundancy.
  • Avoid overusing global modules as it can make dependency management harder to track in very large applications. Only mark modules as global if their services are truly application-wide concerns.

forRoot

In Nexus IoC, the forRoot patterns are used when you want to configure a module globally or initialize it with some options. These patterns are especially useful for modules that need dynamic configuration or initialization, like database modules, authentication, or configuration providers.

Example

import { NsModule } from '@nexus-ioc/core';

interface ConfigOptions {
  apiUrl: string;
}

@NsModule({})
export class ConfigModule {
  static forRoot(options: ConfigOptions) {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options, // Pass the options to the providers
        },
        {
          provide: 'ASYNC_CONFIG_OPTIONS',
          useFactory: async () => {
            return options;
          }
        }
      ],
      exports: ['CONFIG_OPTIONS', 'ASYNC_CONFIG_OPTIONS'], // Export so it can be used globally
    };
  }
}
  • ConfigModule has a forRoot method that accepts a configuration object (ConfigOptions) and registers the options as a provider.
  • The options can be accessed throughout the app using dependency injection.

Usage

import { NsModule } from '@nexus-ioc/core';
import { ConfigModule } from './config.module';

@NsModule({
  imports: [
    ConfigModule.forRoot({ apiUrl: 'https://api.example.com' }),  // Pass options to forRoot
  ],
})
export class AppModule {}

forFeature

forFeature are used to define feature modules that provide specific providers and services to the application. These methods are particularly useful for creating modules that may require additional configuration or initialization, allowing you to organize your codebase into distinct areas of concern.

forFeature Method

The forFeature method is used to create a feature module that registers providers and services related to a specific feature of the application. This method allows you to pass in configuration options that are used to set up the feature providers.

Purpose of forFeature

  • Feature-specific configuration: Provides a way to register providers that are specific to a certain feature within the application.
  • Modular design: Helps organize code by separating concerns and functionalities into different modules.

Example of forFeature

import { NsModule, Injectable } from '@nexus-ioc/core';

interface UserFeatureOptions {
  userRole: string;
}

@Injectable()
class UserService {
  constructor(private options: UserFeatureOptions) {}

  getUserRole() {
    return this.options.userRole;
  }
}

@NsModule({})
export class UserModule {
  static forFeature(options: UserFeatureOptions) {
    return {
      module: UserModule,
      providers: [
        {
          provide: UserService,
          useFactory: () => new UserService(options), // Create UserService with options. can be sync or async
        },
      ],
      exports: [UserService], // Export UserService for use in other modules
    };
  }
}
  • UserModule defines a forFeature method that accepts UserFeatureOptions.
  • The UserService is created using the provided options, which can be accessed by injecting UserService into other components.

Usage

import { NsModule } from '@nexus-ioc/core';
import { UserModule } from './user.module';

@NsModule({
  imports: [
    UserModule.forFeature({ userRole: 'admin' }), // Pass feature-specific options
  ],
})
export class AppModule {}