How to globally inject value across modules in NestJS? - nestjs

I'm working with nx workspace and nestjs.
I would like to inject a value across multiple modules in nestjs app.
Final goal is to reproduce similar way of configuration management as vsavkin mentioned for Angular
But it seems it's not possible, or I missed something.
Nest can't resolve dependencies of the FeatureService (?). Please make
sure that the argument at index [0] is available in the FeatureModule
context.
How can I notify FeatureModule it needs to access to this global injected value ?
This is working fine inside AppService (service in root module), but not in any sub modules.
Here is my code below.
Or an full example on codesandbox.io
app.module.ts
#Module({
imports: [
FeatureModule
],
controllers: [
AppController
],
providers: [
AppService,
{
provide: 'MY-TOKEN',
useValue: 'my-injected-value',
}
],
})
export class AppModule {}
feature.module.ts
#Module({
imports: [],
controllers: [],
providers: [
FeatureService
],
})
export class FeatureModule {
}
feature.service.ts
#Injectable()
export class AppService {
constructor(
#Inject('MY-TOKEN') private injectedValue: string
) {}
}

Quote from NestJS official documentation:
In Angular, the providers are registered in the global scope. Once defined, they're available everywhere. On the other hand, Nest encapsulates providers inside the module scope. You aren't able to use the module providers elsewhere without importing them. But sometimes, you may just want to provide a set of things which should be available always - out-of-the-box, for example: helpers, database connection, whatever. That's why you're able to make the module a global one.
So what you can do is just defining one global module with that MY-TOKEN provider:
#Global()
#Module({
providers: [
{
provide: 'MY-TOKEN',
useValue: 'my-injected-value',
}
],
exports: ['MY-TOKEN'],
})
export class GlobalModule {}
and then you can use its exported values without importing the global module anywhere.

Related

How to get custom provider inside child module from App module?

I have only two modules:
á… Driver
^
|
|
App
In the App module, I add a custom provider (amount)
#Module({
imports: [DriverModule],
controllers: [],
providers: [
{
provide: "amount",
useValue: "200"
}
]
})
and I want to get it in the controller of the Driver module, like this:
#Controller('driver')
export class DriverController {
constructor(#Inject("amount") private amount: number) { .. }
But Nest throws an error:
Nest can't resolve dependencies of the DriverController (?). Please make sure that the argument amount at index [0] is available in the DriverModule context.
Can NestJS get a dependency from another module?
I have no idea how to do it differently
Add amount to exports on App.module
Add AppModule to imports on Driver.module
When you do these, it will probably work, but it is not very correct to provide and use something from App.module hierarchically.

How to define multiple guards on module level in NestJS?

I have a nestjs module where I want to define multiple guards using the APP_GUARD provider. The example in the docs only shows how to define a single guard. How can I define multiple guards?
import { APP_GUARD } from '#nestjs/core';
import { Module } from '#nestjs/common';
#Module({
providers: [
{
provide: APP_GUARD,
useClass: MyFirstGuard, // how can I add a second guard here?
},
],
})
export class AppModule {}
You can provide another guard like this:
providers: [
AppService,
{
provide: APP_GUARD,
useClass: FirstGuard
},
{
provide: APP_GUARD,
useClass: SecondGuard
}
],
You might think because both guards use the same CONSTANT (APP_GUARD) they would overlap, which is true for other provide keys. however using APP_GUARD to register a guard does not mean you can use #Inject(APP_GUARD) to inject it to other modules. Because what is the point? The whole purpose of using APP_GUARD is to register it globally that's why they do not overlap.

Testing svelte components with import.meta.env

I'm struggeling now for a couple of days to get my testsetup running. Rough outline: Vite, Svelte (with ts), Jest.
I'm using import.meta.env.SOMENAME for my environment vars although this works fine for development as soon as a component uses import.meta.env the test will fail with:
SyntaxError: Cannot use 'import.meta' outside a module
I've tried different transformers, babel-plugins and configs but never succeeded...
My jest config:
"jest": {
"globals": {
"ts-jest": {
"isolatedModules": true
}
},
"verbose": true,
"transform": {
"^.+\\.svelte$": [
"svelte-jester",
{
"preprocess": true
}
],
"^.+\\.ts$": "ts-jest",
"^.+\\.js$": "babel-jest"
},
"setupFilesAfterEnv": ["<rootDir>/setupTests.ts"],
"moduleFileExtensions": ["js", "ts", "svelte"]
}
babel.config.js
module.exports = {
presets: [
[
"#babel/preset-env",
{
targets: {
node: "current"
}
}
]
]
};
svelte.config.cjs
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
emitCss: true,
preprocess: sveltePreprocess()
};
Among other things I tried to use #babel/plugin-syntax-import-meta but ended up with the same error. Also vite-jest looked very promising but again I couldn't make it work.
I appreciate every hint I can get. If I can provide any additional info please let me know. Also my knowledge of vite and babel is very limited so REALLY appreciate any help IU can get on this topic.
Update (Solution)
So If you use babel you could use babel-preset-vite. The approach with esbuild-jest from Apu is also good solution that many people use. Unfortunately those things didn't work for me so I decided to use a workaround with vite's define.
This workaround consists of two steps.
replace import.meta.env with process.env (if this is a deal breaker for you then I hope you have luck with the solutions above) You only have to replace the instances in files you want to test with jest.
Update Vite config with define. This step is necessary or your build will break (dev will still work)
vite.config.js
const dotEnvConfig = dotenv.config();
export default defineConfig({
define: {
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
"process.env.VITE_APP_SOMENAME": `"${process.env.VITE_APP_SOMENAME}"`
},
...
)};
I know this is just a workaround but maybe this helps someone. Thanks & Good Luck.
A more recent alternative to Jest that understands import.meta.env is Vitest.
It should require almost no additional configuration to get started and it's highly compatible with Jest so it requires few changes to the actual tests.
The advantages of Vitest over Jest for this use case are:
It's designed specifically for Vite and will process tests on demand
It will reuse your existing Vite configuration:
Any define variables will be replaced as expected
Extensions that Vite adds to import.meta will be available as usual
I was having issues with svelte component testing as well using jest. babel is not good at resolving import.meta. I used esbuild-jest to transform both ts and js files. It solves the issue with the import.meta. Here is my jest.config.cjs.
npm i esbuild esbuild-jest -D
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');
const config = {
"transform": {
"^.+\\.svelte$": [
"svelte-jester",
{
"preprocess": true
}
],
"^.+\\.(ts|tsx|js|jsx)$": ["esbuild-jest"]
},
"moduleFileExtensions": [
"js",
"ts",
"tsx",
"svelte"
],
"setupFilesAfterEnv": [
"#testing-library/jest-dom/extend-expect"
],
"collectCoverageFrom": [
"**/*.(t|j)s",
"**/*.svelte"
],
coverageProvider: 'v8',
"coverageDirectory": "./coverage",
"coveragePathIgnorePatterns": [
"/node_modules/",
"/.svelte-kit/"
],
"moduleNameMapper": pathsToModuleNameMapper(compilerOptions.paths, {prefix: '<rootDir>/'})
};
module.exports = config;

No component factory found for EmailInstructorsComponent. Did you add it to #NgModule.entryComponents?

I am using ngx-admin template. Right now i am trying to create a modal that will open on a button click. I am trying to show a form in the modal window but on clicking, the modal does open but it does not show me the form and i get this error
No component factory found for EmailInstructorsComponent. Did you add it to #NgModule.entryComponents?
I am using lazy laoding and most of the modules have been added in the declaration of a shared.module file to avoid the erros during production.
I tried to look at other links too that were available on the stackoverflow but none of them refered to modal-windows/dialogs that's why i had to create this separate thread
classes-and-students.module.ts file
const ENTRY_COMPONENTS = [
EmailInstructorsComponent
];
#NgModule({
imports: [
ClassesAndStudentsRoutingModule,
NbCardModule,
Ng2SmartTableModule,
NbAlertModule,
SharedModule,
CKEditorModule
],
declarations: [
ClassesAndStudentsComponent,
...routedComponents,
InstructorbiddingComponent,
EmailInstructorsComponent,
],
entryComponents: [
...ENTRY_COMPONENTS
],
providers: [
NewsService,
],
})
export class ClassesandStudentsModule { }
instructorbidding.component.ts file
#Component({
selector: 'ngx-instructorbidding',
templateUrl: 'instructorbidding.component.html',
styleUrls: ['instructorbidding.component.scss']
})
export class InstructorbiddingComponent {
#ViewChild('contentTemplate', { static: true }) contentTemplate: TemplateRef<any>;
#ViewChild('disabledEsc', { read: TemplateRef, static: true }) disabledEscTemplate: TemplateRef<HTMLElement>;
constructor(private windowService: NbWindowService) { }
openWindowForm(contentTemplate) {
this.windowService.open(EmailInstructorsComponent, { title: 'Window'});
}
}
email-instructors.component.ts file
#Component({
selector: 'ngx-email-instructors',
templateUrl: './email-instructors.component.html',
styleUrls: ['./email-instructors.component.scss']
})
export class EmailInstructorsComponent {
constructor(public windowRef: NbWindowRef) {
}
close() {
this.windowRef.close();
}
}
email-instructors.component.html file
<form class="form">
<label for="subject">Subject:</label>
<input nbInput id="subject" type="text">
<label class="text-label" for="text">Text:</label>
<textarea nbInput id="text"></textarea>
</form>
I am not sure what mistake i am making so please help me out by pointing out my mistake
Try to change your code like this to see if it work
const ENTRY_COMPONENTS = [
EmailInstructorsComponent
];
#NgModule({
imports: [
ClassesAndStudentsRoutingModule,
NbCardModule,
Ng2SmartTableModule,
NbAlertModule,
SharedModule,
CKEditorModule
],
declarations: [
ClassesAndStudentsComponent,
...routedComponents,
InstructorbiddingComponent,
EmailInstructorsComponent,
],
entryComponents: [
ENTRY_COMPONENTS
],
providers: [
NewsService,
],
})
export class ClassesandStudentsModule { }

Jest halts when parsing a decorator that returns another decorator

I had been using a graphql decorator on my components (via react-apollo), and in an attempt to make my code more DRY, I decided to wrap the graphql function in another decorator like so:
import { graphql } from 'react-apollo'
export default function wrappedQuery(gqlDocument, numberOfItems) {
return (component) => graphql(gqlDocument, {
options: ...
props: ...
})(component)
}
Happily, this works in browser when I use it like so:
import React from 'react'
import wrappedQuery from './wrappedQuery'
import gqlDocument from './allPosts.graphql'
#wrappedQuery(gqlDocument, 10)
export default class BlogPostContainer extends React.Component {
...
}
However, when I try to run tests with Jest, any test that imports this class returns an error pointed at the BlogPostContainer class declaration
Test suite failed to run
TypeError: Cannot read property 'default' of undefined
at App (src/index.js:10:31)
at Object.<anonymous>(src/pages/BlogPage/containers/BlogPostContainer/index.js:13:53)
When I replace wrappedQuery with the identical graphql decorator, the test runs correctly. But as this query function is 15 lines long and subject to change between many classes, it's not ideal. Like I said, wrappedQuery is working in browser. Maybe this is a jsdom issue? I'm not well enough experienced to know if the problem is with Jest, or babel-jest, or jsdom, or something else...
// Works in browser and in Jest
#graphql(gqlDocument, {
options: ...
props: ...
}
export default class BlogPostContainer extends React.Component {
...
}
I also attempted to use the function as you would without the transform-decorators plugin, but I get the same issue
// Works in browser but not in Jest
class BlogPostContainer extends React.Component {
...
}
export default wrappedQuery(gqlDocument, 10)(BlogPostContainer)
Here are my config files:
// jest.config.js
module.exports = {
moduleFileExtensions: [ 'js' ],
moduleDirectories: [ 'node_modules' ],
moduleNameMapper: {
'^src/': '<rootDir>/src'
},
transform: {
'\\.(gql|graphql)$': 'jest-transform-graphql',
'^.+\\.js$': 'babel-jest'
}
}
// .babelrc
{
"presets": [
[ "env", {
"targets": {
"browsers": [
"last 2 versions"
]
},
"useBuiltIns": "usage",
"debug": true
}],
"stage-0",
"react"
],
"sourceMaps": true
}

Resources