bielas
bielas

Reputation: 712

Angular doesn't see the injected service

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: enter image description here

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

Answers (2)

Alexander Staroselsky
Alexander Staroselsky

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

Brad
Brad

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

Related Questions