Reputation: 712
I want to create kind of interceptor for error handler. That's why I create the files:
ErrorService:
import { Injectable } from '@angular/core';
@Injectable()
export class ErrorService {
name : any;
constructor() {
}
setName(message:string){
this.name = message;
}
getName(){
return this.name;
}
error(detail: string, summary?: string): void {
this.name = detail;
}
}
AppErrorHandler:
import { ErrorService } from './error-service';
import { ErrorHandler, Inject } from '@angular/core';
export class AppErrorHandler implements ErrorHandler {
constructor(@Inject(ErrorService) private errorService: ErrorService){
}
handleError(error : any){
this.errorService.setName(error.status);
console.log('getter', this.errorService.getName());
console.log('test error ', error);
}
}
To that point everything goes quite good. When I print an error in handleError
it prints correctly.
But when I want to pass the ErrorService
object into the ErrorComponent
suddenly Angulas can't see it.
ErrorComponent:
import { ErrorService } from './../common/error-service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.css']
})
export class ErrorComponent implements OnInit {
constructor(private errorService: ErrorService) {
console.log('from ErrorComponent, ', this.errorService);
}
message = this.errorService.getName();
ngOnInit() {
}
}
And ErrorComponent.html
<div class="alert alert-danger">
Error!!! {{message}}
</div>
Of course I have added some imports in app.module
:
import { ErrorComponent } from './error/error.component';
@NgModule({
declarations: [
...
ErrorComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpModule
],
providers: [
PostService,
ErrorService,
{provide: ErrorHandler, useClass: AppErrorHandler}
],
bootstrap: [AppComponent]
})
export class AppModule { }
The problem is that in AppErrorHandler
I am passing the error to the ErrorService
and the I want to display it in ErrorComponent
but ErrorComponent
doesn't see the passed data
UPDATE:
I followed the below solution and get one error. My errorComponent
looks like:
import { ErrorService } from './../common/error-service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.css']
})
export class ErrorComponent implements OnInit {
errorService: ErrorService;
message: string;
constructor(errorService: ErrorService) {
this.errorService = errorService;
console.log('------ from error component ', errorService.name, this.errorService);
}
ngOnInit() {
}
}
And the html
file:
<div class="alert alert-danger">
Error!!! {{errorService.name}}
</div>
And the problem is that I can't actually print the name property in the browser. In chrome console I have:
so you can see that I can easliy get the whole object ErrorService but couldn't get the property name which in console is undefined
- why? And because of that I can't display it properly.
Upvotes: 0
Views: 609
Reputation: 38847
Here is how to resolve your issue:
1) To be able to effectively inject ErrorService
into AppErrorHandler
, you will want to decorate AppErrorHandler
with Injectable()
. With that you can remove the @Inject(ErrorService)
and simply inject the service in the standard fashion.
import { ErrorService } from './error-service';
import { ErrorHandler, Injectable } from '@angular/core';
@Injectable()
export class AppErrorHandler implements ErrorHandler {
constructor(private errorService: ErrorService){}
}
2) In AppErrorHandler
you are targeting a property status
of the error object. Keep in mind that standard error objects have properties such as message
, name
, description
, and number
depending on the browser. You may want to check whether the status
property exists before attempting to get it's value, otherwise you could get undefined
for non HTTP errors.
Update: make sure to call super(true);
and super.handleError(error);
or throw error;
to ensure errors are re-thrown and default error handling occurs after your custom error handling. Otherwise the errors will be swallowed by the custom error handler.
export class AppErrorHandler implements ErrorHandler {
// ...
handleError(error : any) {
// rethrow exceptions
super(true);
if(error.status) {
console.log(error.status);
this.errorService.setName(error.status);
}
else {
console.log('Message: ', error.message);
console.log('Description: ', error.description);
this.errorService.setName(error.message);
}
console.log('Getter: ', this.errorService.getName());
// trigger default error handling after custom error handling
super.handleError(error);
}
}
3) In your error component, to actively see the changes to the name
property of the ErrorService
, at minimum you'd want to make the name
property a public property. Then in the ErrorComponent, you'd target the injected service's name
property to display. A more scalable approach would be using RxJS Subject for example to emit changes that the ErrorComponent
can subscribe to. Doing message = this.errorService.getName();
will not reset/react to changes to the name property automatically when errors occur. You'd need to either attach to the public property and/or use observables or similar to emit changes to components to subscribe to. See Update 2 below for example of how to utilize RxJS Subject to update message
.
Service:
import { Injectable } from '@angular/core';
@Injectable()
export class ErrorService {
public name : any;
constructor() {}
ErrorComponent HTML
<div class="alert alert-danger">
<!-- Angular will detect changes to the services' -->
Error!!! {{errorService.name}}
</div>
Here is a plunker demonstrating the functionality. Click the button to trigger an error, in this case calling a method that does not exist.
Update: console.log('------ from error component ', errorService.name, this.errorService);
in the constructor is logging undefined
because at the time the constructor()
runs, the public property name
has no default value. If you want a default value, you can set that in ErrorService
doing public name: string = 'foobar';
. The plunker has been updated to show the logging before and after name
of ErrorService
is being set. If you add a default value in ErrorService
you'll see your log, prints the name
property value.
Update 2: If you absolutely need to use message
on ErrorComponent
rather than any other property for displaying the value, including the ErrorService
's public name
property, you can use Subject and Observable to subscribe to emitted changes to update the name property similar to Parent/Child Interaction. This would involve instantating a new Subject on ErrorService
, exposing public Observable property and subscribing to those changes. This shows how to use specifically message
property of ErrorComponent
getting values from ErrorService
and it's name
property. You may not really need the name property if the only goal is to display the error in ErrorComponent
. Additionally in the plunker, it is displaying the HTTP error status code in the message
property of ErrorComponent
.
@Injectable()
export class ErrorService {
public name: string;
// create new Subject
private nameSource = new Subject<any>();
// expose Observable property
error$ = this.nameSource.asObservable();
setName(message: string){
this.name = message;
// emit error
this.nameSource.next(this.name);
}
// ...
}
@Component({ // ... })
export class ErrorComponent {
message: string = '';
constructor(private errorService: ErrorService) {
// same stuff as before
// subscribe to Observable from ErrorService and update name property on changes
this.errorService.error$.subscribe(name => {
this.message = name;
});
}
}
@Injectable()
export class AppErrorHandler extends ErrorHandler {
private errorService: ErrorService;
constructor(errorService: ErrorService) {
super(true);
this.errorService = errorService;
console.log(this.errorService);
}
handleError(error : any) {
if(error.status) {
console.log(error.status);
this.errorService.setName(error.status);
}
else {
console.log('Message: ', error.message);
console.log('Description: ', error.description);
this.errorService.setName(error.message);
}
}
}
Here is an updated plunker.
Hopefully that helps!
Upvotes: 1
Reputation: 4553
You may need to use forwardRef()
import { Inject, Injectable, forwardRef } from "@angular/core";
export class AppErrorHandler implements ErrorHandler {
constructor(@Inject(forwardRef(() => ErrorService)) private errorService: ErrorService){
}
...
}
Upvotes: 0