Angular - How to implement Exception Handling on component level - javascript

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

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

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.

Should I send JS exceptions manually to Google Analytics?

I am new to Google Analytics. I have an Angular 4 application. I would like to know when an uncaught JavaScript exception had been thrown. I've set up a Google Analytics via Google Tag Manager. It works perfectly for history change event (navigation between Angular routes). But it doesn't fire JS errors.
And here is what I see in debug pane:
You can see an exception in console but JS Error tag is not fired. What am I doing wrong?
Ok, here is what I found. You shouldn't expect that exceptions will be fired automatically. You should implement ErrorHandler and manually throw event from there.
import {ErrorHandler, Injector} from '#angular/core';
import {Angulartics2GoogleTagManager} from 'angulartics2/gtm';
export class MyErrorHandler implements ErrorHandler {
private analytics: Angulartics2GoogleTagManager;
constructor(private injector: Injector) {
}
handleError(error: Error) {
console.error(error);
if (!this.analytics) {
this.analytics = this.injector.get(Angulartics2GoogleTagManager);
}
this.analytics.eventTrack(JSON.stringify(error.message), {
event: 'jsError',
label: JSON.stringify(error.stack).replace('\n', ' ')
});
}
}
app.module.ts:
{provide: ErrorHandler, useClass: MyErrorHandler, deps: [Injector]},

Angular module - Error encountered resolving symbol values statically

I've created the below module, following this guide:
#NgModule({
// ...
})
export class MatchMediaModule {
private static forRootHasAlreadyBeenCalled: boolean = false;
// The method is used for providing the
// feature module's providers only ONCE
static forRoot(): ModuleWithProviders {
if (this.forRootHasAlreadyBeenCalled) {
throw new Error('ModuleWithProviders - forRoot() should only be called once in the root module!');
}
this.forRootHasAlreadyBeenCalled = true;
return {
ngModule: MatchMediaModule,
providers: [MatchMediaService],
};
}
}
After importing the MatchMediaModule module into the CoreModule:
#NgModule({
imports: [
CommonModule,
MatchMediaModule.forRoot() // <--
],
})
export class CoreModule { }
and running ng serve, the following error is thrown:
ERROR in Error encountered resolving symbol values statically. Calling
function 'MatchMediaModule', function calls are not supported.
Consider replacing the function or lambda with a reference to an
exported function, resolving symbol CoreModule in
/Users/alex/www/mdello-port/src/app/core/core.module.ts, resolving
symbol CoreModule in
/Users/alex/www/mdello-port/src/app/core/core.module.ts
However, after making some minor change, causing the cli to recompile the app, everything runs without an issue.
Removing the forRoot() method and providing the service directly also seems to work. Nevertheless, I'd like to preserve the benefits of guarding against multiple singleton instance creation during lazy loadings of the mentioned module.
Is there a way to fix this error without compromising the aforementioned benefit?
Rewriting the forRoot() in the following way has remedied the issue:
#NgModule({
// ...
})
export class MatchMediaModule {
// ** REMOVED **
// The method is used for providing the
// feature module's providers only ONCE
static forRoot(): ModuleWithProviders {
// ** REMOVED **
return {
ngModule: MatchMediaModule,
providers: [MatchMediaService],
};
}
}
Seems like the problem was caused by side effects of the forRoot() method.

When is #Inject optional in Angular?

I have a simple service in Angular
// service.ts
export class SimpleService {
// ...
}
// component.ts
#Component({
selector: 'my-component',
templateUrl: 'components/mycomp/mycomp.html',
providers: [
SimpleService
]
})
class MyComponent {
constructor(private ss: SimpleService) {}
}
This above code never works. I get an error: Uncaught Error: Can't resolve all parameters for MyComponent: (?).
However, if I change my constructor definition to:
class MyComponent {
constructor(#Inject(SimpleService) private ss: SimpleService) {}
}
Then it works. Even documentation doesn't seem to use #Inject. From the documentation I understand that #Inject is explicitly required when my provider token is not class; like, trying to inject primitive values, or using opaque tokens, etc.
Also, I am confused in regards to typescript. Official documentation clearly mentions that when #Inject() is not present, Injector will use the type annotation of the parameter. If type information is erased during typescript transpilation and DI is happening when app is running, how can Angular use type annotation at runtime? Is there anything I am missing?
You forgot to add
"emitDecoratorMetadata": true
to your tsconfig.json
See also
angularjs 2.0: Can't inject anything through component constructor()

Categories