【问题标题】:Angular 2: How to handle async errors from services in components?Angular 2:如何处理组件中服务的异步错误?
【发布时间】:2016-06-23 15:00:36
【问题描述】:

假设我们有一个对 API 进行 http 调用以创建用户的服务。根据结果​​(200 或错误),应用程序应重定向或显示错误(客户端进行了一些验证,但这不是主题,因为验证也应始终发生在服务器端)。

Angular 2 文档指出,让服务返回一个可观察对象并在组件中订阅它是不好的做法。该服务应该是自包含的,并且组件不需要对此有任何了解。拨打userService.createUser(user_data);应该就够了

但是路由应该再次发生在组件中。所以我想知道如何处理这种情况?我的服务应该返回一个新的 Observable 吗?还是只是一个值?但是那我该如何处理异步任务呢?

这样做的正确方法是什么?

我们以这个服务创建用户和这个组件为例:

// user.service.ts:

import { Http, Headers } from '@angular/http';
import { Inject, Injectable } from '@angular/core';

import { API_CONFIG, Config } from '../config/api.config';
import { User } from '../models/user.model';

@Injectable()
export class UserService {
  validation_errors: Array<any> = [];

  constructor(
    @Inject(API_CONFIG) private api_config: Config,
    private http: Http,
    @Inject(User) public user: User
  ) {}

  createUser(user: User) {
    var body = JSON.stringify({ user });
    var myHeader = new Headers();
    myHeader.append('Content-Type', 'application/json');

    this.http.post(this.api_config.apiEndpoint + '/users/', body, { headers: myHeader })
      .map(res => res.json())
      .subscribe(
        res => {
          // User was created successfully
          this.user = this.fromJson(res.data);
        },
        err => {
          // Something went wrong, let's collect all errors in a class attribute
          let errors = err.json().errors;
          for(var i = 0; i < errors.length; i++) {
            this.validation_errors.push(errors[i])
          }
        }
      );
  }

  /**
  * @param input_json JSON returned from API, formatted according to jsonapi.org, containing one single user.
  * @return UserModel instantiated with the values from input_json
  */
  fromJson(input_json: any) {
    var user:User = new User();
    user = input_json.attributes;
    user.id = input_json.id;
    return user;
  }
}

// user.component.ts:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, REACTIVE_FORM_DIRECTIVES, Validators } from '@angular/forms';
import { Router, RouteParams, ROUTER_DIRECTIVES } from '@angular/router-deprecated';

import { UserService } from '../../shared/index';

@Component({
  selector: 'fac-user-editor',
  templateUrl: 'app/+users/editor/user-editor.component.html',
  styleUrls: ['app/+users/editor/user-editor.component.css'],
  directives: [REACTIVE_FORM_DIRECTIVES, ROUTER_DIRECTIVES],
  providers: [UserService]
})
export class UserEditorComponent implements OnInit {

  // Setup Form
  private email_regex = '[a-z0-9\\.\\-\\_]+@[a-z0-9\\.\\-\\_]+\\.[a-z0-9\\.\\-\\_]+';
  userForm: FormGroup;
  action: string;
  idCtrl = new FormControl('');
  nameCtrl = new FormControl('', [Validators.required]);
  emailCtrl = new FormControl('', [Validators.required, Validators.pattern(this.email_regex)]);
  usernameCtrl = new FormControl('', [Validators.required, Validators.minLength(5)]);
  passwordCtrl = new FormControl('', [Validators.minLength(8)]);
  passwordConfirmationCtrl = new FormControl('');

  public user_id: string;


  constructor(private userService: UserService, private router: Router, private params: RouteParams) {}

  /**
  * Handle submit of form
  */
  onSubmit(form: any) {
    // Here should happen some error handling / routing, depending on the result of the call to the API
    this.userService.createUser(this.userService.user);
  }

  ngOnInit(): any {

    this.userForm = new FormGroup({
      id: this.idCtrl,
      name: this.nameCtrl,
      email: this.emailCtrl,
      username: this.usernameCtrl,
      password: this.passwordCtrl,
      password_confirmation: this.passwordConfirmationCtrl
    });

  }

}

我可以像这样在模板中显示错误消息:

<div class="form-group"
  [hidden]="adminService.validation_errors.length === 0"
  class="alert alert-warning" role="alert">
  <strong>Some errors occured</strong>
  <ul>
    <li *ngFor="let validation_error of adminService.validation_errors">
      <span class="text-capitalize">{{validation_error.source.field}}:</span> {{validation_error.detail}}
    </li>
  </ul>
</div>

【问题讨论】:

  • 您的示例可以工作,但如果其他人(可能是其他服务?)使用adminService 并产生错误怎么办?您的组件会在右侧显示该错误吗?
  • @Springrbua:我的例子是一个不好的例子,不应该这样做(即使它有效)。我想使用 Observables 重构它,同时遵循 google 的最佳实践指南。
  • 您可以将一个空数组validation_errors 传递给该方法并用错误填充它,以便每次调用都返回它自己的结果。你也可以使用回调函数,但我记得 Angular2 使用 Observables 等,因为它们比 x 级回调更容易阅读……我自己使用 Observables 并订阅它们。我看不出有什么理由不应该在组件中使用它们。我的意思是 Angular2 有一个 async Pipe 是为 Observables 和 Promises 制作的
  • 我真的不明白管道如何帮助我处理异步错误。你能举一个返回 Observable 的例子吗?
  • 我只是想说,angular2 提供了一个为可观察对象(异步管道)制作的管道。所以我不明白为什么你不应该在你的组件中使用 observables。

标签: javascript angular


【解决方案1】:

这就是我解决问题的方法。我的服务现在使用 .map() 来转换从后端返回的数据 (JSON) 并返回一个 Observable,它将初始化我的用户模型。组件可以订阅 observable。数据存储在组件的一个变量中,以及验证错误。

我还使用 FormBuilder 形成新的 Form API。这应该适用于 RC 3。

我希望这对某人有所帮助。

// user.service.ts:

import { Http, Headers } from '@angular/http';
import { Inject, Injectable } from '@angular/core';

import { API_CONFIG, Config } from '../config/api.config';
import { User } from '../models/user.model';

@Injectable()
export class UserService {

  constructor(
    @Inject(API_CONFIG) private api_config: Config,
    private http: Http,
    @Inject(User) public user: User
  ) {}

  createUser(user: User) {
    var body = JSON.stringify({ user });
    var myHeader = new Headers();
    myHeader.append('Content-Type', 'application/json');

    // Use map to transform reply from server (JSON to User Object) and return an Observable 
    // The component can subscribe to.
    return this.http.post(this.api_config.apiEndpoint + '/users/', body, { headers: myHeader })
      .map(res => res.json())
      .map(res => {
        return this.fromJson(res.data);
      });

  }

  /**
  * @param input_json JSON returned from API, formatted according to jsonapi.org, containing one single user.
  * @return UserModel instantiated with the values from input_json
  */
  fromJson(input_json: any) {
    var user:User = new User();
    user = input_json.attributes;
    user.id = input_json.id;
    return user;
  }
}

// user.component.ts:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, REACTIVE_FORM_DIRECTIVES, Validators } from '@angular/forms';
import { Router, RouteParams, ROUTER_DIRECTIVES } from '@angular/router-deprecated';
// Include the User Model
import { User, UserService } from '../../shared/index';

@Component({
  selector: 'fac-user-editor',
  templateUrl: 'app/+users/editor/user-editor.component.html',
  styleUrls: ['app/+users/editor/user-editor.component.css'],
  directives: [REACTIVE_FORM_DIRECTIVES, ROUTER_DIRECTIVES],
  providers: [User, UserService]
})
export class UserEditorComponent implements OnInit {

  // Setup Form
  private email_regex = '[a-z0-9\\.\\-\\_]+@[a-z0-9\\.\\-\\_]+\\.[a-z0-9\\.\\-\\_]+';
  userForm: FormGroup;
  validation_errors: Array<string> = [];
  user_id: string;

  constructor(private userService: UserService, private user: User, private router: Router, private params: RouteParams, private formBuilder: FormBuilder) {}

  /**
  * Handle submit of form
  */
  onSubmit(form: any) {
    // Subscribe to observable and handle errors
    this.userService.createUser(this.userService.user).subscribe(
      user => {
        // Store created user in Component variable. We could now display it or navigate somewhere
        this.user = user;
      },
      err => {
        let errors = err.json().errors;
        for(var i = 0; i < errors.length; i++) {
          // Handle errors in the component and don't store it in the Service
          this.validation_errors.push(errors[i])
        }
      }
    );
  }

  ngOnInit(): any {
    // Use the new FormBuilder
    this.userForm = this.formBuilder.group({
      id: [''],
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.pattern(this.email_regex)]],
      username: ['', [Validators.required, Validators.minLength(5)]],
      password: ['', [Validators.required, Validators.minLength(8)]],
      password_confirmation: ['']
    });

  }

}

user-editor.component.html

<div class="form-group"
  [hidden]="validation_errors.length === 0"
  class="alert alert-warning" role="alert">
  <strong>Some errors occured</strong>
  <ul>
    <li *ngFor="let validation_error ofvalidation_errors">
      <span class="text-capitalize">{{validation_error.source.field}}:</span> {{validation_error.detail}}
    </li>
  </ul>
</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 2015-04-15
    • 2015-07-18
    • 2019-03-21
    • 1970-01-01
    • 2017-11-04
    相关资源
    最近更新 更多