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

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

Related

Why and how add Model as a dependency in NestJS?

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

Angular: manually instantiate class with dependency injection

I am using Angular 10.0 and I have a problem with --prod compiling.
I need to instantiate classes manually and need to support dependency injection.
The following code works fine during development to instantiate my classes:
public instantiateWithDi(parentInjector: Injector, myClass: any): any {
const reflector = ReflectiveInjector.resolveAndCreate([], parentInjector);
const newInstance = reflector.resolveAndInstantiate(myClass);
return newInstance;
}
When I build my project with --prod (or --optimization=true), then I get the following error at runtime:
ERROR Error: Cannot resolve all parameters for 'e'(?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'e' is decorated with Injectable.
Decorating the constructor parameters of the classes with #Inject did not work either. Using injection tokens does not help as well.
The classes are already decorated with #Injectable() and in the "providers" array of their respective angular module.
I know, the ReflectiveInjector is deprecated, but simply using the get method of the injector does not work either, because it seems to cache the classes once created and does not re-instantiate them each time I call my "instantiateWithDi" method.
Example usage
I've created a small demo at stackbliz for this: https://stackblitz.com/edit/angular-plugin-mechanism?file=src/app/plugin-execution.service.ts
Basically the magic happens here (plugin-execution.service.ts):
#Injectable({
providedIn: 'root'
})
export class PluginExecutionService {
public static readonly eventListeners = [];
constructor(private injector: Injector){}
private instantiateWithDi(parentInjector: Injector, myClass: any): any {
const reflector = ReflectiveInjector.resolveAndCreate([], parentInjector);
const newInstance = reflector.resolveAndInstantiate(myClass);
return newInstance;
}
public onApplicationEvent(event: ApplicationEvent){
const injector = Injector.create({
parent: this.injector,
providers: [{
provide: ApplicationEvent,
useValue: event
}]
});
PluginExecutionService
.eventListeners
.forEach(cls => this.instantiateWithDi(injector, cls));
}
}
This allows developers to create a class and push their class into a eventListener array. It gets executed every time, an application event occurs.
See the example "plugin" some.plugin.ts in the stackblitz example.
The real usecase is of course much more complex and involves custom decorators and stuff, but that would be quite an overkill for a demo.
You see the result in the console. The "plugins" work fine as intended. But when i build it using --prod, the app does not work any longer...
Any help is very much appreciated!
Thanks,
Manuel

nestjs exception filter is not invoked

I am trying to use an exception filter in my NestJS app. I followed the instructions found here to setup my global ExceptionFilter, which looks like this:
#Catch()
export class DemoExceptionFilter implements ExceptionFilter
{
private readonly logger: Logger;
constructor()
{
this.logger = new Logger(DemoExceptionFilter .name);
}
public catch(exception: unknown, host: ArgumentsHost): void
{
this.logger.log(exception);
}
}
In my AppModule I have registered the DemoExceptionFilter this way:
#Module({
...
providers: [
...
{
provide: APP_FILTER,
useClass: DemoExceptionFilter
}
...
]
})
When I throw an exception somewhere in my code that exception gets logged by NestJS in the console but my DemoExceptionFilter is not invoked.
I also tried
app.useGlobalFilters(new DemoExceptionFilter());
in main.ts, which also does not work.
What am I missing?
In the documentation, it says where global exception filters will be applied:
Global-scoped filters are used across the whole application, for every controller and every route handler.
They are not used for the application startup. So if you want to test your exception filter, throw an error in the route handler method of a controller and call the route.

Using factory to create controller

I was wondering whether I can use a factory to initialize a controller and then add it to a module. Code could look something like this, but this is not working:
const controllerFactory = {
provide: DefinitionController,
useFactory: async (service: DefinitionService) => {
//initialization of controller
return new DefinitionController();
},
inject: [DefinitionService],
};
#Module({
controllers: [controllerFactory],
providers: [DefinitionService],
})
export class DefinitionModule {}
It looks like using factories for controllers is not supported, but I am not sure. There is an example of using factory for providers, but I cannot find anything for controller in documentation or on google.
It's not possible to define your controller with an async factory comparable to custom providers. You cannot add dynamic endpoints/routes unless using the native express/fastify instance:
At the moment there is no way to register a route dynamically except
by using the internal HTTP / Fastify / Express instance
There is an issue where a dynamic routing module is discussed but this will probably not be part of nest very soon:
At the moment both Kamil and I are really busy, so this issue may take
some time - except someone else takes on the task :)
But you can use the OnModuleInit lifecycle event to do static initialization:
#Injectable()
export class DefinitionController implements OnModuleInit {
onModuleInit() {
console.log(`Initialization...`);
}
It will be called once when your app starts and has access to the injected providers in your controller, e.g. your DefinitionService.

Angular - How to implement Exception Handling on component level

While working inside Angular (Angular 4, 5), if a component raises Error (TypeError or null or undefined error or so), whole application breaks onward.
How can we deal with this, to catch errors on component level and possibly show a fallback UI, like React16 does using Error Boundaries.
I would approach it by handling the error at Component level and have a service that listens to any errors happening at Component or Service level.
Ex:
Throw the error from the service
catch the error in component
Handle the error, process it and send the Error event with details to ErrorService.
You can have a app level component "errorBannerComponent" which takes input from ErrorService and paint your UI.
As soon as the error is received in ErrorService, The errorBannerComponent should display the error on screen.
Hope it helps.
Also By default, Angular comes with its own ErrorHandler that
intercepts all the Errors that happen in our app and logs them to the
console, preventing the app from crashing. We can modify this default behavior by creating a new class that implements the ErrorHandler:
You can find more details and example here:
As the proposed solutions are rather dull. I tried to recreate it myself. The easiest solution would be to provide a module scoped custom ErrorHandler class.
Thanks to this, you could even create a multiple different ErrorBoundaries.
My proposed solution can be seen here: https://stackblitz.com/edit/angular-ivy-brb143?file=src/app/widget/widget.module.ts
What is really important for this solution to work (atleast it didn't work otherwise for me). Was to provide the custom error handler as a part of a module rather than a component directly.
The important bits from the solutions:
module:
/**
* This is really imporant as this allows us to provide a module scoped ErrorHandler
*/
#NgModule({
imports: [CommonModule],
declarations: [WidgetComponent],
providers: [{ provide: ErrorHandler, useClass: WidgetErrorHandler }],
exports: [WidgetComponent],
})
export class WidgetModule {}
component where we can throw, and catch error
#Component({
selector: 'app-widget',
templateUrl: './widget.component.html',
styleUrls: ['./widget.component.css'],
})
export class WidgetComponent implements OnInit {
constructor(#Inject(ErrorHandler) public widgetError: WidgetErrorHandler) {}
ngOnInit() {
this.widgetError.isError$.subscribe((error) =>
console.log('component can act on error: ', error)
);
}
public handleThrowErrorClick(): void {
throw Error('Button clicked');
}
}
and the handler iself
#Injectable()
export class WidgetErrorHandler implements ErrorHandler {
public isError$: Subject<Error | any> = new Subject();
handleError(error) {
console.log('Intercepted error', error);
this.isError$.next(error);
}
}

Categories