Glauco Todesco
Glauco Todesco

Reputation: 15

Angular, ErrorHandler and BehaviorSubject: update message only second click

I am trying to implement a general http handler error, but I found a strager behavior. My Error component only update the message error only after second click, but I dont know why.

The code is available here in StackBlitz: enter link description here

In app.component.ts on click I try a http request and it will fail, just to throw an error.

 onClick() {
    this.http
      .get('http://localhost:8080/test')
      .pipe(catchError(this.errorHandler))
      .subscribe(() => {});
  }

  errorHandler(error: HttpErrorResponse) {
    return throwError(error.message);
  }

All errors are handled in global.error.ts and each error is add in error.service.ts:

export class GlobalErrorHandler implements ErrorHandler {
  constructor(private errorService: ErrorService) {}

  handleError(error: Error) {
    this.errorService.add('New Error');
  }
}

The error.service.ts uses BehaviorSubject and notify my error.component.ts:

export class ErrorService {
  public notification = new BehaviorSubject<string>('Initial Value');

  add(err: string) {
    this.notification.next(err);
  }
}

Finally, the error.component.ts must update the message error on screen, but it works only in the second click, but the console.log works perfect.

export class ErrorComponent implements OnInit {
  public errorMessage: string = '';

  constructor(public errorService: ErrorService) {}

  ngOnInit() {
    this.errorService.notification.subscribe({
      next: (notification) => {
        console.log('Error Component');
        this.errorMessage = notification;
      },
    });
  }
}

Questions:

  1. Any reason for this behavior?
  2. Any suggestions for implementing a Global Handler Http Error?

Upvotes: 0

Views: 330

Answers (1)

Chris Hamilton
Chris Hamilton

Reputation: 10944

You just need to force change detection using the ChangeDetectorRef

  constructor(
    public errorService: ErrorService,
    private cd: ChangeDetectorRef
  ) {}

  ngOnInit() {
    this.errorService.notification.subscribe({
      next: (notification) => {
        console.log('Error Component');
        this.errorMessage = notification;
        this.cd.detectChanges();
      },
    });
  }

https://stackblitz.com/edit/angular-ivy-tzt7xk?file=src/app/error/error.component.ts

Alternatively, wrapping the .next() call with NgZone.run() also lets change detection pick up the changes within its subscriptions.

  constructor(private ngZone: NgZone) {}

  public notification = new BehaviorSubject<string>('Initial Value');

  add(err: string) {
    this.ngZone.run(() => {
      this.notification.next(err);
    });
  }

I'd suggest just displaying the subject directly with the async pipe as well.

{{ errorService.notification | async }}

https://stackblitz.com/edit/angular-ivy-wknur7?file=src/app/error.service.ts

More information here: Angular 6 View is not updated after changing a variable within subscribe.

In summary, Angular only updates the view if the variable is changed within the "Angular Zone". The second time you press the button, it is just then picking up the first value, not the second one.

Upvotes: 2

Related Questions