Angular HttpClient错误处理需要zone.run()来更新UI?

时间:2018-04-17 01:28:11

标签: angular error-handling httpclient

我正在开发一个简单的Angular登录对话框,它使用HttpClient将post()发布到我的后端API。我严格遵循angular httpclient docs中规定的方法。

一切正常,除非发生错误(我故意关闭后端API以生成504)。即使在这种情况下,所有错误处理代码都会按照我的预期执行,但问题是角度UI无法更新。

在正确执行的错误处理代码中,我正在更新组件中的一个简单字符串变量,该变量通过标准插值绑定到对话框UI;但是改变永远不会出现在用户界面中。

这是我的服务代码,用于调用HttpClient(注意:我正在使用pipe()和catchError()并返回文档中建议的新ErrorObservable):

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import 'rxjs/add/operator/timeout';
import { catchError} from 'rxjs/operators';
import { Observable } from 'rxjs/Observable';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
// My models
import { SignupRequest } from '../models/signuprequest';
import { LoginRequest } from '../models/loginrequest';
import { LoginResponse } from '../models/loginresponse';
import { SignupResponse } from '../models/signupresponse';
import { ErrorResponse } from '../models/errorresponse';

@Injectable()
export class LoginService {

  private readonly loginUrl = 'http://localhost:4200/api/login';
  private readonly signupUrl = 'http://localhost:4200/api/signup';
  private readonly timeout = 20000;

  constructor(private http: HttpClient) {}

  LoginUser(loginRequest: LoginRequest) {
       console.log('LoginService: login request ', loginRequest);
       return this.http.post<LoginResponse>(this.loginUrl, loginRequest, { observe: 'response' })
            .timeout(this.timeout)
            .pipe(catchError(this.HandleError));
  }

  private HandleError(error: HttpErrorResponse, caught: Observable<any>) {
        const er: ErrorResponse = {
          response: error,
          url: error.url ? error.url : '',
          status: error.status ? error.status : '',
          statusText: error.statusText ? error.statusText : '',
          message: error.message ? error.message : '',
          error: error.error ? error.error : ''
        };
        console.log('LoginService: error: ', er);
        return new ErrorObservable(er);
  }
}

以下是调用服务调用并订阅生成的Observable的组件代码:

import { Component, OnInit, Injectable, ApplicationRef, NgZone } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpResponse } from '@angular/common/http';

import { LoginService } from '../../services/login.service';
import { SignupRequest } from '../../models/signuprequest';
import { LoginRequest } from '../../models/loginrequest';
import { LoginResponse } from '../../models/loginresponse';
import { SignupResponse } from '../../models/signupresponse';
import { ErrorResponse } from '../../models/errorresponse';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['../../app.component.css']
})

export class LoginComponent implements OnInit {
    ...

    constructor( private fb: FormBuilder, private loginService: LoginService ) {
        ...
    }

    public Login() {
      const loginRequest = this.ExtractLoginRequest();
      if (this.ValidLogin(loginRequest)) {
        console.log('LoginComponent: login request ', loginRequest);
        this.loginService.LoginUser(loginRequest).subscribe(
            this.HandleLoginResponse,
            // The line below should work fine based on everything I've read
            this.HandleError
            // But the UI will not update unless I use the zone.run() line below instead!? 
            // (error) => { this.zone.run( () => { this.HandleNetworkError(error); } ); }
      );
      console.log('finished the login request flow');
    }

    private HandleLoginResponse(httpResponse: HttpResponse<LoginResponse>) {
      const loginResponse: LoginResponse = httpResponse.body;
      if (!this.BackEndLoginError(loginResponse)) {
        console.log('LoginComponent: Storing access token in localstorage', loginResponse.token);
        window.localStorage.setItem('littlechatToken', loginResponse.token);
      }
    }

    private HandleError(errorResponse: ErrorResponse) {
      console.log('LoginComponent: handling error');
      // set the error text that is displayed on a div in the UI
      this.backendLoginErrorText = 'Network error: ' + errorResponse.message;
    }

}

在组件模板中,我进行插值以显示&#39; backendLoginErrorText&#39;可变...

<!-- Login button and error text -->
<div class="centered">
  <button type="button" [disabled]="LoginDisabled()" name="login" (click)="Login()">Login</button>
  <div class="alert alert-danger" [hidden]="backendLoginErrorText.length === 0"> {{backendLoginErrorText}} </div>
</div> 

正如我在组件代码中的代码注释中所指出的,只是将this.HandleError作为错误处理函数传递给subscribe()不起作用 - UI永远不会更新!注意:所有代码都正确执行(即更新肯定发生,更改检测无效)。

如果我改为使用this.zone.run(...),如上所述,更新发生就好了。

在阅读了SO以及其他内容之后,我理解了这里发生了什么的基础知识 - 事实上,在几秒钟之后异步发生504错误会导致错误处理被执行在Angular之外,需要zone.run()?

我正在学习Angular,一位经验丰富的朋友告诉我&#34;如果你曾经在这种情况下使用zone.run(),那么你做错了什么&#34;。我发现很难相信这样一个简单的场景(带有后端API和post()调用的基本登录表单) - 使用angular docs中建议的方法 - 需要这样一个看似hacky的东西...... / p>

我在这里缺少什么?

0 个答案:

没有答案