Julius
Julius

Reputation: 2864

Angular string variable not bound to template

I have an Angular app containing an HttpInterceptor and an ErrorHandler. Errors intercepted by the HttpInterceptor are then handled by the ErrorHandler. The ErrorHandler pushes error messages onto a Subject, to which AppComponent is subscribed.

In this way I am hoping to display HTTP errors in the view of AppComponent.

The general approach seems to work, because I receive error messages in AppComponent when I call https://example.com/404 (= 404 error). However, the error message is not shown in the view.

HttpInterceptor:

@Injectable({providedIn: "root"})
export class GlobalHttpInterceptorService implements HttpInterceptor {

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((err) => {
        console.log("Error in GlobalHttpInterceptorService");
        return throwError(err);
      }),
    );
  }
}

ErrorHandler:

@Injectable({providedIn: "root"})
export class GlobalErrorHandlerService implements ErrorHandler {

  public subject = new Subject<string>();

  handleError(error: any) {
    console.log('Error in GlobalErrorHandlerService');
    this.subject.next(error.message)
  }
}

AppComponent (with template):

@Component({
  selector: 'my-app',
  template: 'The error: {{ errorMessage }}',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'test';
  errorMessage: string = 'none';

  constructor(
    private service: Service,
    private errorHandlerService: GlobalErrorHandlerService
  ) {
    this.errorHandlerService.subject.subscribe((message) => {
      console.log('Error in AppComponent: ' + message);
      this.errorMessage = message;
    });

    // Call non existent URL = 404 error
    this.service.getNonExistentUrl().subscribe(() => console.log('bla'));
  }
}

Running the above I see this in the browser console:

enter image description here

Actually the GET call fails because of browser policies, which is fine. Any error will do to demonstrate the real problem:

In AppComponent I can console.log() the error message, but the message isn't bound to the view. errorMessage is still "none":

enter image description here

Stackblitz example: https://stackblitz.com/edit/angular-ivy-zikova?file=src/app/app.component.ts

Upvotes: 0

Views: 40

Answers (1)

Lars
Lars

Reputation: 395

The handleError() is called outside of the Angular Zone so the change detection will not detect any changes inside the component. You can use the run() method from NgZone to avoid the problem.

Inject the NgZone into the GlobalErrorHandlerService and call the next() inside the run method.

constructor(private zone: NgZone) { }

handleError(error: any) {
  this.zone.run(() => {
    this.subject.next(error.message);
  });
}

Another option would be to use OnPush change detection in your AppComponent. Set the changeDetection to ChangeDetectionStrategy.OnPush and inject the ChangeDetectorRef. Inside the subscriber call the detectChanges() method to detect the changes.

constructor(
  private service: Service,
  private errorHandlerService: GlobalErrorHandlerService,
  private changeDetectorRef: ChangeDetectorRef
) {
  this.errorHandlerService.subject.subscribe((message) => {
    console.log('Error in AppComponent: ' + message);
    this.errorMessage = message;
    this.changeDetectorRef.detectChanges();
  });

  this.service.getNonExistentUrl().subscribe(() => console.log('bla'));
}

Upvotes: 2

Related Questions