Why and how add Model as a dependency in NestJS? - javascript

I'm trying to do this tutorial and it doesn't work for me. When I run the code, I get this error:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the TestService (?, AnimalsModel). Please make sure that the argument StudentModel at index [0] is available in the TestService context.
Potential solutions:
- If StudentModel is a provider, is it part of the current TestService?
- If StudentModel is exported from a separate #Module, is that module imported within TestService?
#Module({
imports: [ /* the Module containing StudentModel */ ]
})
I believe this is the code that I need to change(?)
#Injectable()
export class TestService {
constructor(
#InjectModel('Student') private readonly studentModel: Model<Student>,
#InjectModel('Animals') private readonly animalModel: Model<Animal>,
) {}
Git Repo

you've got TestService in an imports array. DOn't do that. provides never belong in the imports array, only modules do
To create the provider for #InjectModel('Student') you need to add MongooseModule.forFeature([{ name: 'Student', schema: StudentSchema })]) to the imports array of the module that contains TestService (presumably TestModule) so that Nest can go and create the dynamic provider that you want to inject.
Edit after receiving the repository
You're using named database connections so you need to use those same connections in the #InjectModel(). #InjectModel('Student', 'myWorldDb'). Just like the docs show

Related

NestJS CQRS: (Repository's) Dependency Injection not working in handler

I am new to nodejs and am trying to implement NestJS's CQRS 'recipe'. I have a service with Request scope with QueryBus injection:
#Injectable({scope: Scope.REQUEST})
export class CustomerService {
constructor(
#Inject(REQUEST) private readonly req: Request,
private readonly queryBus: QueryBus,
) {}
I have defined a handler class CustomerHandler to handle CustomerQuery:
#QueryHandler(CustomerQuery)
export class CustomerHandler implements IQueryHandler<CustomerQuery> {
constructor(
private readonly repository: CustomerRepository,
) {}
async execute(query: CustomerQuery) {
const {response, id, name} = query;
this.repository.getCustomer(response, id, name);
}
But upon execution I got an error message:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'getCustomer' of undefined at CustomerHandler.execute
which means, if I am not wrong, repository injection failed. (which caused code to fail for statement this.repository.getCustomer(response, id, name);)
I have added my repository class CustomerRepository in providers array of my module CustomerModule:
#Module({
imports: [Logger, CqrsModule],
providers: [CustomerService, ...QueryHandlers, CustomerRepository],
exports: [CustomerService],
})
export class CustomerModule {}
Here's my repository class, for reference:
#Injectable()
export class CustomerRepository {
constructor(
#Inject(REQUEST) private readonly req: Request,
) {}
Am I missing something here? Why is my repository class not being instantiated, if thats not the case, why is the repository injection failing in handler. Any input would be appreciated :)
Documentaion I am following: https://docs.nestjs.com/recipes/cqrs
Github example I referred: https://github.com/kamilmysliwiec/nest-cqrs-example
EDIT:
Handler (CustomerHandler) is not able to perform any sort of injection. I tried injecting logger (PinoLogger), which led to similar issue. So, it looks like, the problem is not with CustomerRepository, but with CustomerHandler.
UPDATE:
So basically, the problem is that every CqrsModule provider is
statically scoped which mean that they cannot depend on request-scoped
providers. Once you define your command handler as a request-scoped
provider, either CommandBus or QueryBus won't be able to reference it.
This is not an issue, but rather a design decision that sits behind
the entire module.
Source: https://github.com/nestjs/cqrs/issues/60#issuecomment-483288297
i.e. #QueryHandler() cannot be request scoped (source: comment on question - NestJS undefined dependencies and answer to the same https://stackoverflow.com/a/61916353/10011503).
And, this is also an open issue.
Reading nestjs doc, i saw that all handlers for command and query handlers are resolve en default scope, so, all dependencies with request or trasient scope are not provide in handlers. Solution is inject factory objects that resolve dependencies when are necesary

Creating Inject decorator for module that's registered multiple times

I'm trying to create a module with a service and would like to create a custom Inject decorator for it.
Let's call the Module a Store module, which provides and exports a StoreService and has to have a name. This is the Module:
import { Module } from '#nestjs/common';
import { StoreService } from './store.service';
#Module({})
export class StoreModule {
static register(
name: string,
): DynamicModule {
return {
module: StoreModule,
providers: [
{
provide: STORE_NAME,
useValue: name,
},
StoreService,
],
controllers: [],
exports: [StoreService],
};
}
}
The 'name' is what makes the store unique.
Now I'd like to create a decorator that fetches the store by name, like #InjectStore(name). I've tried to create a token based on the name, like STORE_SERVICE_${name} and injecting that, but the StoreModule needs to be registerred globally for that.
When I set global: true, it causes weird behaviour inside the module itself. Because StoreService is registered multiple times for multiple stores. For example, when I have a store cats and a store dogs, and I create a service inside the module and inject the store via constructor(private readonly store: StoreService), it'll get one of the two.
A workaround for this behaviour is probably to use the #InjectStore decorator, but because I came across this behaviour, I'm wondering if I'm missing something or if there's a better solution, because I want to make sure it injects the right store.
I've searched far and wide for an example implementation in NestJS modules, but just couldn't find what I was looking for.
Thanks in advance!

Using Injected Token to configure a lazily loaded module

Consider the following snippet. How do I access the provided value of Environment in forRoot? Is there an alternate way that I do the same?
interface Environment {
tokens: Record<string, string>;
}
const ENVIRONMENT = new InjectionToken<Environment>('ENVIRONMENT');
// `SomeModule` with `forRoot` to configure it. This module is from an external library.
// Some library module that I'm creating
#NgModule({
imports: SomeModule.forRoot() // `tokens` needs to passed here.
})
export class MyLibraryModule {}
MyLibraryModule will be consumed in some application where ENVIRONMENT will be injected. It will be lazily loaded

Using Angular to declare a new service instance per module?

I'm using angular. I already know that when an appmodule is importing modules which declares providers, the root injector gets them all and the service is visible to the app - globally. (I'm not talking about lazy loaded modules)
But is it possible that each module will have its own instance of the service?
I thought of maybe something like this :
#NgModule({
providers: [AService]
})
class A {
forRoot() {
return {
ngModule: A,
providers: [AService]
}
}
forChild() {
return {
ngModule: A,
providers: [AService]
}
}
}
But I don't know if it's the right way of doing it
Question
How can I accomplish service per module ?
STACKBLITZ : from my testing , they are using the same service instance
When we provide a service in a feature module that is eagerly loaded
by our app's root module, it is available for everyone to inject. - John Papa
So looks like there is no way to inject at feature-module level, as there are no module level injectors other than the one at root module.
But angular has nodes at each component level for the injector, so such a scenario will have to use coponent level-injectors I guess.
You can also have a parent component inject the service for different children sharing the same instance.
One way is to provide the services at component level. Not sure if that will work for you.
Also, check the multiple edit scenario in the docs
https://angular-iayenb.stackblitz.io
import { Component, OnInit } from '#angular/core';
import {CounterService} from "../../counter.service"
#Component({
selector: 'c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css'],
providers:[CounterService]
})
export class C2Component implements OnInit {
constructor(private s:CounterService) { }
ngOnInit() {
}
}
Question
How can I accomplish service per module ?
Not with the default Injector. Default Injector keeps nodes at root level and component level, not at feature-module level. You will have to have a custom Injector if there is a real scenario.
Edit: the previous answer from me is not completely correct. The correct way to make this work is to use lazy loaded modules, provide the services there. The given service should not be provided with a static forRoot() method somewhere because then the lazy loaded module will access the root injector.
There is no actual reference to do so because that is not how angular is designed but if you want it that way you have to the opposite of
https://angular.io/guide/singleton-services#providing-a-singleton-service
and
https://angular.io/guide/singleton-services#forroot
Old not completely correct:
You simple have to declare the service you want to be single instance for each and every module within the providers meta data of each and every module. Then Angular will not reuse any instance of the service.
Giving scenario: You have two modules, ModuleA and ModuleB, both need the service but different instance, then you will declare them in the providers section of ModuleA and ModuleB.
Reference:
https://angular.io/guide/singleton-services

How do I declare a dependency on an ng-metadata module?

I have a project that is using ng-metadata (https://github.com/ngParty/ng-metadata) to build a handful of Angular 1.5 modules. I have a test module/component that looks like this:
import { NgModule, Component, Inject, Input, Output, EventEmitter } from 'ng-metadata/core'
import { platformBrowserDynamic } from 'ng-metadata/platform-browser-dynamic'
#Component({
selector: 'test',
template: require('./test.template.html')
})
class TestComponent {
#Input() type: string;
constructor() {
console.log(`test: ${this.type}`)
}
}
#NgModule({
declarations: [TestComponent]
})
class HeroModule {}
platformBrowserDynamic().bootstrapModule(HeroModule)
Everything seems happy when compiled and I'm now attempting to use the module in another project (that is not using ng-metadata but has a compatible version of Angular).
I'm simply including the shims as directed by the ng-metadata docs and the JavaScript file that contains the module described above (built by webpack). I have a new module in this project that wants to list the HeroModule as a dependency. I've tried a few things:
// attempt 1:
angular.module('my-consuming-module', ['ui.router', 'hero'])
// attempt 2:
angular.module('my-consuming-module', ['ui.router', 'heroModule'])
// attempt 3:
angular.module('my-consuming-module', ['ui.router', 'hero-module'])
All always end up with the same Error: $injector:nomod Module Unavailable error from Angular.
If I'm using ng-metadata to build my modules, what are the names I use to list them as dependencies in another project?
Finally figured this out! It's amazing what happens when you carefully read documentation...
Found in the Manual Angular 1 Bootstrap section of ng-metadata's docs:
You can still leverage ng2 way of components registration without ng-metadata bootstrap, but you have to manually create your Angular 1 module from an ng-metadata #NgModule using the bundle helper function.
I ended up being able to do the following:
// REMOVED platformBrowserDynamic().bootstrapModule(HeroModule)
const Ng1AdminModule = bundle(HeroModule).name;
export const AppModule = angular.module('hero', [Ng1AdminModule]);
And then my hero module becomes accessible to the my-consuming-module just as I expected. The bundle helper function was the key to figuring this out.
You need to import those module from their respective locations and inject it inside your angular module
//ensure `angular`, `angular-ui-router` should be there in `map` of systemjs.config.js
import * as angular from 'angular';
import * as uiRouter from 'angular-ui-router';
import { heroModule} from './hero.module';
angular.module('app',[ uiRouter, heroModule]);
Check references here

Categories