NestJS: Enforce implementation of a service at *compile-time* - javascript

To keep it brief:
I have a service - let's call it CatsService...
import { Injectable } from '#nestjs/common';
#Injectable()
export class CatsService {
GetCat(id: number){
return id + 12345;
}
}
Later on, a module wishes to use this CatsService in order to inject it into a constructor. It also dynamically resolves this CatsService
import { Module } from '#nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import * as ProductionCatsService from "./production"
import * as DevelopmentCatsService from "./development"
const catsServiceProvider = {
provide: CatsService,
useValue: process.env.NODE_ENV === "development" ?
DevelopmentCatsService :
ProductionCatsService
}
#Module({
controllers: [CatsController],
providers: [catsServiceProvider],
exports:[CatsService]
})
export class CatsModule {}
Both of these service implementations are strictly empty. This means that the /cats/:id route returns:
{"statusCode":500,"message":"Internal server error"}
I have had the idea since beginning writing this to make everything implement a ICatsService interface.
After a lot of fiddling about, I have build a sort-of solution:
I have a "service_interface" file, which has an abstract class called ICatsInterface with one abstract method (GetCat)
I have made every one of these implementations implement this service_interface.
I have made the controller take ICatsService instead of CatsService
My problem is that now, the thing compiles, but I get the below error:
TypeError: this.catsService.GetCat is not a function
at CatsController.get (/home/a/learning-nest/src/cats/cats.controller.ts:12:33)
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-execution-context.js:38:29
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-proxy.js:9:17
I simply don't know what to do anymore. Even though I have made it compile, I cannot make it actually be forced to implement the interface/abstract class that I want. I have made sure that both DevelopmentCatsService and ProductionCatsService have the implementation of GetCat. They are both marked with #Injectable. I don't truthfully know where to go from here.
Should I abandon my dream of having an compile-time error message for when someone does not implement the methods that I want implemented in my service?

I have solved my own issue. It was very simple.
import { Module } from '#nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import * as ProductionCatsService from "./production"
import * as DevelopmentCatsService from "./development"
const catsServiceProvider = {
provide: CatsService,
useValue: process.env.NODE_ENV === "development" ?
DevelopmentCatsService :
ProductionCatsService
}
#Module({
controllers: [CatsController],
providers: [catsServiceProvider],
exports:[CatsService]
})
export class CatsModule {}
In the above snippet, I am importing "* as ProductionCatsService". This caused my ProductionCatsService to actually be an object wrapping around the real service implementation that I wanted!
Sorry for the bother, all.

Related

webpack error angular wrapper for native lib

I have created a Service for an Angular App which is working fine as expected.
Its a Notification Service Wrapper for the package 'awesome-notifications'.
If I transfer the Service to the lib and use it from the lib i am getting kind of an import error its telling me something like constructor is not defined. Its referencing to the line :
'import AWN from "awesome-notifications"' which is the first line of the service.
its needed to create the notifier in the Service. Because its a Wrapper.
it seems like angular can not inject the service i wrote.
But if i am using it in an existing app it works, its really strange. does someone have experience with that ?
Code
import { Injectable } from '#angular/core';
import AWN from 'awesome-notifications';
export interface NotificationRequestModel {
message:string;
title?:string;
}
#Injectable({
providedIn: 'root'
})
export class NotificationService {
notifier = new AWN();
constructor() {
}
success(req:NotificationRequestModel) {
this.notifier.success(req.message, {
labels: { success: req?.title },
})
} etc..
ERROR TypeError: awesome_notifications__WEBPACK_IMPORTED_MODULE_0__ is not a constructor
This looks like a build/bundle issue with webpack/typescript. Since one can't reproduce your setup you should provide an example with more information. Its probably the default import for your native library. I can only guess:
Do you have allowSyntheticDefaultImports set in your ts config?

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

Inject nestjs service from another module

I've got a PlayersModule and an ItemsModule.
I want to use the ItemsService in the PlayersService.
When I add it by injection:
import { Injectable } from '#nestjs/common';
import { InjectModel } from 'nestjs-typegoose';
import { ModelType, Ref } from 'typegoose';
import { Player } from './player.model';
import { Item } from '../items/item.model';
import { ItemsService } from '../items/items.service';
#Injectable()
export class PlayersService {
constructor(
#InjectModel(Player) private readonly playerModel: ModelType<Player>,
private readonly itemsService: ItemsService){}
I get this nest error :
[Nest] 11592 - 2018-8-13 11:42:17 [ExceptionHandler] Nest can't
resolve dependencies of the PlayersService (+, ?). Please make sure
that the argument at index [1] is available in the current context.
Both modules are imported in the app.module.ts. Both services are working alone in their module.
You have to export the ItemsService in the module that provides it:
#Module({
controllers: [ItemsController],
providers: [ItemsService],
exports: [ItemsService]
^^^^^^^^^^^^^^^^^^^^^^^
})
export class ItemsModule {}
and then import the exporting module in the module that uses the service:
#Module({
controllers: [PlayersController],
providers: [PlayersService],
imports: [ItemsModule]
^^^^^^^^^^^^^^^^^^^^^^
})
export class PlayersModule {}
⚠️ Don't add the same provider to multiple modules. Export the provider, import the module. ⚠️
Let' say you want to use AuthService from AuthModule in my TaskModule's controller
for that, you need to export authService from AuthModule
#Module({
imports: [
....
],
providers: [AuthService],
controllers: [AuthController],
exports:[AuthService]
})
export class AuthModule {}
then in TaskModule, you need to import AuthModule (note: import AuthModule not the AuthService in TaskModule)
#Module({
imports:[
AuthModule
],
controllers: [TasksController],
providers: [TasksService]
})
export class TasksModule {}
Now you should be able to use DI in TaskController
#Controller('tasks')
export class TasksController {
constructor(private authService: AuthService) {}
...
}
The question is answered by Kim Kern. But I just want to remind people who read through this comment. Whenever you get this error, you should follow these steps that may help you easily figure out where the stuck is:
Make sure the Module which provides providers was imported.
Make sure the provider which you are using is exported.
For example, you have category module which contains category service, post module has post service and it has category service as a dependency:
#Module({
controllers: [CategoryController],
providers: [CategoryService],
exports: [CategoryService] // Remember to export
})
export class CategoryModule {}
And
#Module({
imports: [CategoryModule], // Make sure you imported the module you are using
controllers: [PostController],
providers: [PostService]
})
export class PostModule {}
Don't forget to use this annotation.
Nest uses this to detect singleton class.
In spring boot - Java, this one used to be called Bean. Read more:
#Injectable()
export class PostService {
constructor(private readonly categoryService: CategoryService // This will be auto injected by Nestjs Injector) {}
}
I solved my problem by removing #Inject() from the argument in my constructor that was passing the exported service.
I believe that you faced the same problem i had. My scenario was 2 sibling custom modules (user, auth) that needed to use each other's services. I used circular DI to solve it. please check this link
Let me know whether if it solved your issue, maybe I can advise you further.
Solved my problem by changing the way of importing the constant string (TOKEN) used in #Inject()) of my provider... be careful using index.ts whith export * from module.ts, nest won't resolve the dependecy
Based on the answer by Kim Kern nowadays we should add only injected service into our service without any decorators (#Inject() doesn't required). After that it will work right. That was my mistake and probably can help others.
Steps 1. Export the file that you want
Step 2. Import the whole module.
I initially made a mistake of adding the file as provider and also adding the module which was throwing error.

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;
};
...

How to check for isBrowser in Angular 4

In the past you could use isBrowser from Angular Universal to check if your page was rendering in a browser (and therefore you can use things like localStorage) or if it was doing server side pre-rendering.
But it seems that angular2-universal was dissolved into #angular/core, #angular/platform-server and #angular/platform-browser.
I've looked for similar functionality in the API documentation for Angular 4 and also tried to find it somewhere in the source code, but without luck.
Am I missing something or what is the Angular 4 way of checking if the rendering is running in a browser? Or should I simply just check if window is defined?
import { PLATFORM_ID, Inject } from '#angular/core';
import { isPlatformBrowser} from '#angular/common';
...
export class MyComponent {
...
testBrowser: boolean;
constructor(
#Inject(PLATFORM_ID) platformId: string) {
this.testBrowser = isPlatformBrowser(platformId);
if (this.testBrowser) {
//this is only executed on the browser
}
}
...
You can import isPlatformBrowser(<platform id>) as so:
import { isPlatformBrowser } from '#angular/common';
and that will allow you to check for whether it is rendering in browser or not.
As a note, there is also a isPlatformServer in #angular/common as well.
Do the Following steps:
import { isPlatformBrowser } from '#angular/common';
import { Inject, PLATFORM_ID } from '#angular/core;
constructor(#Inject(PLATFORM_ID) platformId: Object){
const isBrowser = isPlatformBrowser(this.platformId);
}
I hope it helps:

Categories

Resources