Globle module without import not working nestjs - javascript

I am trying to implement a global module in nest.js
I have created a service like below
export interface ConfigData {
DB_NAME: string;
}
#Injectable()
export class ConfigManager {
private static _instance?: ConfigManager;
public static configData:ConfigData | null;
private constructor() {
console.log(ConfigManager)
if (ConfigManager._instance)
ConfigManager._instance = this;
else{
ConfigManager._instance = new ConfigManager()
ConfigManager.configData = <ConfigData|null><unknown>ConfigManager.fetchSecretData()
}
}
private static async fetchSecretData():Promise<ConfigData|null>{
// some data from db
}
// static get instance() {
// return ConfigManager._instance ?? (ConfigManager._instance = new ConfigManager());
// //return ConfigManager._instance ?? (ConfigManager._instance = ConfigManager.fetchSecretData()) //new ConfigManager());
// }
}
configuration.module.ts
#Global()
#Module({
providers: [ConfigManager],
exports: [ConfigManager],
})
export class ConfigurationModule {}
and in app.module.ts added ConfigurationModule in imports.
Also adding private constructor on service unable it to add in module.ts file.
I am expecting that I should be able to configData anywhere without importing the ConfigManager. but it's not working...
ConfigManager is not available without import.

You've only marked your module with #Global decorator, but the NestJS needs to somehow initialize that module and make it globally available.
What this means is that you have to add this module to your core application module and NestJS will do the rest for you, so something like this (or however your root module is named):
#Module({
imports: [ConfigurationModule],
})
export class AppModule {}
From the documentation
The #Global() decorator makes the module global-scoped. Global modules
should be registered only once, generally by the root or core module.

Global modules in nest only means you don't have to include that module in the imports: [] of every other module that requires it's providers. The providers in the global module still behaves as as normal providers i.e. you need to inject it where you need it.
So in your case since you have already added #Global() to ConfigManager and imported ConfigurationModule in app.module.ts, you will not need to add ConfigurationModule in imports for any other modules who want to use ConfigManager. You would, however, still need to inject the ConfigManager provider - that's what #Injectable() means :).
To do the injection, you need a constructor(private configManager: ConfigManager) {} in the class that needs to use ConfigManager, and since you need access to the type of the class, you'll need import { ConfigManager } from '../path/to/ConfigManager' as well.

this is 'per-design' of es6/ts: you cant use the class without importing it.
you are mixing the concepts of di (instantiation/composition) with importing (defining which classes are available in the module scope)

Related

Webpack bundle interface implementation depending on environment variable

In a TypeScript application that uses Webpack for bundling, we have an interface such as:
interface IMyInterface {
method(): void
}
And two implementations which require different dependencies.
import IMyInterface
import package-for-foo;
class FooImplementation implements IMyInterface {
method(): void { ... }
}
import IMyInterface
import package-for-bar;
class BarImplementation implements IMyInterface {
method(): void { ... }
}
My idea is to have two different target bundles, one containing FooImplementation with package-for-foo dependency, and other bundle containing BarImplementation containing package-for-bar dependency.
Additionally, when running, the implementation of which IMyInterface would be used would be resolved at initialization (as it cannot change) and be a global variable accross the application, so clients could simply do impl.method() regardless of which implementation was initialized.
I am able to somewhat accomplish the running part by using an envvar and with webpack DefinePlugin creating a global variable. Then using this global variable at initialization to instantiate the proper implementation. After resolving the global variable, I'll do:
ImplControl.ts file
import FooImplementation from "./FooImplementation ";
import BarImplementation from "./BarImplementation ";
let impl: IMyInterface;
if (globalVar === "foo") {
impl = new FooImplementation ();
} else {
impl = new BarImplementation ();
}
export default impl;
However, they are all being bundled together still since they are somewhat coupled by the imports. Is there any way to make this happen as I want to?

NestJS - Inject factory provider into another provider doesn't work

I'm trying to use nest custom provider with factory, for some reason I'm struggling to get it working.
I've created the following sample -
interface ProviderOptions: {x:string, y:number};
interface AsyncProps {useFactory:(...args:any[])=> ProviderOptions, inject:any[], imports:any[] }
#Module({})
export class MyModule {
static forRootAsync(asyncProps: AsyncProps): DynamicModule {
const myFactory: Provider = {
provide: "MY_PROVIDER",
useFactory: asyncProps.useFactory,
inject: asyncProps.inject,
};
return {
module: MyModule,
providers: [myFactory, MyService],
exports: [MyService],
imports: [...asyncProps.imports],
};
}
}
#Injectable()
export class MyService {
constructor(#Inject("MY_PROVIDER") this options:ProviderOptions){
}
}
For some reason I'm unable to resolve MyService -
Error: Nest can't resolve dependencies of the MyService (?). Please
make sure that the argument dependency at index [0] is available in
the MyModule context.
what I'm missing here?
Thanks!
UPDATE -
so now it's really strange -
originally MyService was in a different file, next to MyModule. when moved MyService to the same file as MyModule, the above code does work as expected. how can it be?
Based on your error, you have a circular file import which means that typecript can't resolve the class name and in turn Nest can't create the class. I just answered another question on it here

process.env.varible undefined in nest.js

I am using nest.js and I want to import object defined in auth.parameters.ts file. File is defined as below. It appears environmental variable is not visible unless below variable is declared in class.
export const obj = {
somevar : process.env.CUSTOM_VAR,
};
In my other classes I would like to import the file with import {SecurityParameters } from '../auth/auth.parameters'
and access the variable with console.log(SecurityParameters.somevar).
I can access the variable directly using process.env.CUSTOM_VAR or
I get undefined if I use somevar : process.env.CUSTOM_VAR in other file.
As you're using Nest's ConfigModule, then unless you're creating a custom config file you shouldn't be doing anything with process.env. Instead, you should be injecting Nest's ConfigService where you need the environment variables and using this.configService.get('CUSTOM_VARIABLE') (or whatever value you need). To get started with this, you need to import the config module
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
#Module({
imports: [ConfigModule.forRoot()],
})
export class AppModule {}
You can pass { isGlobal: true } to the forRoot() call to make the ConfigService injectable anywhere in your application.
If you want a different approach where you can use the process.env object instead, you can add this as the first two lines of your main.ts
import { config } from 'dotenv';
config();
Nest calls this (or something similar) under the hood in the ConfigService, but that doesn't make it immediately available.

Importing TypeScript modules with dependency injection in NestJS

In my NestJS application - I have TypeScript classes that have other classes and values injected into them. The only thing is that I'm importing the TypeScript classes with import statements, and also using the DI system to inject them. Is there some way to remove the import statements and just let the DI system handle it?
TL;DR
import -> class reference
DI -> class instantiation
Matching by string token is possible, but class reference is preferred.
Encapsulation
The dependency injection system mainly handles the instantiation of the classes. This is great, because you do not need to care about the transitive dependencies that the class you want to inject requires.
Example: I want to use the UserService in my UserController. The UserService requires the UserModel for instantiation. However, this second-level dependency is hidden in the UserController. This is great because when the UserService gets a new dependency like a LoggingService, the UserController does not have to be changed.
So instead of
class UserController {
constructor() {
const userModel = new UserModel();
this.userService = new UserService(userModel);
}
}
you can just do
class UserController {
// the transitive dependency on UserModel is hidden
constructor(private userService: UserService) {}
}
Class Reference
But for the DI to know which service to inject you need some link from the #Inject declaration to an actual class to instantiate. Of course, this mechanism depends on the implementation of the DI system. The reference could be by name (string matching), by interface (DI decides which implementation to use: UserService -> UserServiceImpl / MockUserServiceImpl) or in the default case of nestjs directly by the class to be instantiated.
Although matching by name is possible in nestjs, matching by class is preferred because it makes refactoring much easier.
When you create a custom provider you can choose what kind of token you want to use for the matching. This is needed, when you want to inject a value (no class for matching)
const connectionProvider = {
provide: 'Connection',
useValue: connection,
};
#Module({
providers: [connectionProvider],
})
or a dynamically instantiated class.
const configServiceProvider = {
provide: ConfigService,
useClass: process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
#Module({
providers: [configServiceProvider],
})

Ionic3 / Ionic2+ settings.json file for environment variable configuration [duplicate]

I'm working on an ionic2 project and I need to create a new custom JSON config file. I found some tutorials to create one and access it through http.get but I think it's weird to call it through a get request. I want it in the root folder (where all the config JSONs are) and I open/read the file directly.
I don't know if it's possible, or even recommended ? This is why I'm posting here to have some opinions and solutions :)
Thanks
Personally I don't like the read the config.json file by using the http.get way to handle configuration information, and even though there must be another way to just include and read the json file in your code, since we're using Angular2 and Typescript, why not using classes, interfaces and doing it in a more fancy way?
What I'll show you next may seem more complicated than it should at first (although after reading it you will find it very straightforward and easy to understand), but when I started learning Angular2 I saw an example of how they handled config files in the Dependency Injection guide and I followed that in the apps I've worked on to handle config information (like API endpoints, default values, and so on).
According the docs:
Non-class dependencies
[...]
Applications often define configuration objects with lots of small
facts (like the title of the application or the address of a web API
endpoint) but these configuration objects aren't always instances of a
class.
One solution to choosing a provider token for non-class dependencies
is to define and use an OpaqueToken
So you would need to define a config object with the urls and so on, and then an OpaqueToken to be able to use it when injecting the object with your configuration.
I included all my configuration in the app-config.ts file
// Although the ApplicationConfig interface plays no role in dependency injection,
// it supports typing of the configuration object within the class.
export interface ApplicationConfig {
appName: string;
apiEndpoint: string;
}
// Configuration values for our app
export const MY_CONFIG: ApplicationConfig = {
appName: 'My new App',
apiEndpoint: 'http://www...'
};
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
What OpaqueToken is may be confusing at first, but it just a string that will avoid naming conflicts when injecting this object. You can find an amazing post about this here.
Then, you just need to include it in the page you need it like this:
import { NavController } from 'ionic-angular/index';
import { Component, OpaqueToken, Injectable, Inject } from "#angular/core";
// Import the config-related things
import { MY_CONFIG_TOKEN, MY_CONFIG, ApplicationConfig } from 'app-config.ts';
#Component({
templateUrl:"home.html",
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
})
export class HomePage {
private appName: string;
private endPoint: string;
constructor(#Inject(MY_CONFIG_TOKEN) private config: ApplicationConfig) {
this.appName = config.appName;
this.endPoint = config.apiEndpoint;
}
}
Please notice how to include it in the providers array
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
And how to tell the injector how it should obtain the instance of the config object
#Inject(MY_CONFIG_TOKEN) config: ApplicationConfig
UPDATE
OpaqueToken has been deprecated since v4.0.0 because it does not support type information, use InjectionToken<?> instead.
So instead of these lines:
import { OpaqueToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
Now we should use
import { InjectionToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new InjectionToken<ApplicationConfig>('config');
After reading and reading different solutions I ended up using this hacky implementation. Hopefully there will be a nice and native solution available soon:
import { NgModule } from '#angular/core';
import { environment as devVariables } from './environment.dev';
import { environment as testVariables } from './environment.test';
import { environment as prodVariables } from './environment.prod';
export function environmentFactory() {
const location = window.location.host;
switch (location) {
case 'www.example.org': {
return prodVariables;
}
case 'test.example.org': {
return testVariables;
}
default: {
return devVariables;
}
}
}
#NgModule({
providers: [
{
provide: 'configuration',
useFactory: environmentFactory
}
]
})
export class EnvironmentsModule {}
and then where ever needed, e.g.:
import { Injectable, Injector, Inject } from '#angular/core';
import { AuthenticationService } from '../authentication';
#Injectable()
export class APIService {
private http: Http;
private apiURL: string;
protected authentication: AuthenticationService;
constructor(
public injector: Injector,
#Inject('configuration') public configuration: any
) {
this.http = injector.get(Http);
this.authentication = injector.get(AuthenticationService);
this.apiURL = configuration.apiURL;
};
...

Categories

Resources