网站搜索

typescript - 在 Angular 中如何处理响应?


这是一个关于基本问题的提问,但我不知道如何根据操作结果向组件返回响应:success 或 error,以便我可以执行与它们相关的操作。

我有一个组件 SignInComponent,它从表单收集所有必要的信息并将其发送到我的 AuthService,AuthService 处理我的 GraphQL 请求(目前还处理重定向和其他事情)。对于请求成功的情况,到目前为止一切正常,但是如果 API 返回任何错误响应,我需要在我的 SignInComponent 中获取这些错误及其相关信息。(例如,如果凭证错误,我应该通知用户)

我尝试返回这样的值:

signIn.subscribe({
        next: // actions for successful response,
        error: (err) => {return 'errors'} // from here
      })

而且我还尝试像这样抛出一个错误:

signIn.subscribe({
        next: // actions for successful response,
        error: (err) => {throw new Error('Oops')} // The Error
      })

并尝试在我的 SignInComponent 中这样捕获它:

      try {
        this.authService.signIn(params);
      } catch (e) {
        // handling the error accordingly 
      }

但是上述所有提到的都没有帮助我在我的 SignInComponent 中获得响应,也没有让我收到结果状态的通知。

这是我的代码,我希望有人能告诉我如何使用良好实践来处理这种情况。

auth.service.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { SignInMutation, SignInInput } from './sign-in.graphql';
import { SignUpMutation, SignUpInput } from './sign-up.graphql';
import { BehaviorSubject } from 'rxjs';

// TODO: To learn if observable is unsubscribed in services automatically. In Classes there is an Inteface OnDestroy for that

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  auth: BehaviorSubject<boolean> = new BehaviorSubject(false);

  constructor(
    private router: Router,
    private signInMutation: SignInMutation,
    private signUpMutation: SignUpMutation
  ) {
    this.isSignedIn();
  }

  signIn(params: SignInInput) {

    this.signInMutation.mutate(params)
      .subscribe({

        next: ({ data }) => {

          const signIn = data.signIn;
          const token = signIn.token;

          const { payload } = JSON.parse(atob(token.split('.')[1]));
          const currentUser = payload.userData;

          const localData = {
            currentUser,
            token
          };

          this.setLocalData(localData);

          this.router.navigate(['/dashboard']);
          this.auth.next(true);

        },

        error: (err) => {
          throw new Error('Ooops...');
        }

      });



  }

  signUp(SignUpInput: SignUpInput) {

    this.signUpMutation.mutate({ SignUpInput })
      .subscribe({
        next: console.log,
        error: console.log,
      });

  }

  private getLocalData(name: string): null | object | string {
    const data = localStorage.getItem(name);
    if (!data) { return null; }

    if (/^(\{).*(\})$/i.test(data)) {
      return JSON.parse(data);
    } else {
      return data;
    }
  }

  private setLocalData(data: object) {

    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        const value = data[key];

        if (typeof value === 'object') {
          localStorage.setItem(key, JSON.stringify(data[key]));
        } else {
          localStorage.setItem(key, data[key]);
        }

      }
    }

  }

  signOut() {
    localStorage.removeItem('token');
    localStorage.removeItem('currentUser');
    this.router.navigate(['/']);
    this.auth.next(false);
  }

  private isSignedIn() {
    const token = this.getLocalData('token') as string;
    const checkFormat = (token && token.split('.').length === 3) ? true : null;

    if (token && checkFormat) {
      this.auth.next(true);
    }

  }

}

登录组件

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AuthService } from '../auth.service';
import { emailFormat } from '../../_helpers/custom-validation';

@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.component.html',
  styleUrls: ['./sign-in.component.scss']
})
export class SignInComponent implements OnInit {

  signInForm: FormGroup;
  submitted: boolean;

  constructor(
    private fb: FormBuilder,
    private authService: AuthService,
  ) { }

  ngOnInit(): void {
    this.signInForm = this.fb.group({
      email: ['', [emailFormat]],
      pwd: [''],
    });

    this.reset();
  }

  onSubmit(e) {
    e.preventDefault();
    this.submitted = true;

    if (this.signInForm.valid) {
      const params = {
        email: this.signInForm.value.email,
        password: this.signInForm.value.pwd
      };

      this.authService.signIn(params);

      this.reset();
    }

  }

  private reset() {
    this.signInForm.reset();
    this.submitted = false;
  }

}

提前感谢大家!

答案#1

所以这更多是一个 rxjs 问题,而不是 Angular 问题。

this.signInMutation.mutate(params) 返回一个包含服务器响应包装数据的 Observable。在你的情况下,让 signin() 方法订阅这个 observable 并不是一个好主意。

相反,常见的做法是直接返回可观察对象:

signIn(params: SignInInput) {
  return this.signInMutation.mutate(params);
}

现在你可能会说有一些数据后处理工作你想做,比如你在原来的 subscribe() 子句中做的那些事情。

好的,让我们把它们移入 pipe()

function signIn(params: SignInInput) {
  return this.signInMutation.mutate(params).pipe(
    tap(data => {
      const signIn = data.signIn;
      const token = signIn.token;

      const { payload } = JSON.parse(atob(token.split('.')[1]));
      const currentUser = payload.userData;

      const localData = {
        currentUser,
        token,
      };

      this.setLocalData(localData);

      this.router.navigate(['/dashboard']);
      this.auth.next(true);
    }),
  );
}

好了,现在让我们回到你的组件代码

onSubmit(e) {
  // ...

  this.authService.signIn(params).subscribe({
    error: err => {
      // handle your err here, where the handler belongs
      this.errorMessage = 'oops, errors here! ' + err.message
    }
  })
}

基本上,这是在 Angular 中处理基于 rxjs 的远程响应的最常见做法之一。当然,还有一些其他需要注意的事情,比如当你的组件被销毁时需要对这个订阅调用 unsubscribe()。但我不想离题。

让你的 Angular 服务直接返回 observables 也有助于你与其他服务或基于 rxjs 的候选项(例如响应式表单)进一步共享和组合它们。

答案#2

由于您在 AuthService 中已经有一个 auth 布尔标志,您可以选择添加一个带有错误信息的 error 标志。

您的组件或其他服务可以订阅它以获取当前状态。

这种方法(以状态管理的方式),可以给你更多的灵活性,以获取不同的状态(认证是/否,错误/无错误...)。你也可以添加一个加载标志,在认证过程中设置为是。

export class AuthService {
  auth = new BehaviorSubject<boolean>(false);
  error = new BehaviorSubject<string>(null);

  auth$ = this.auth.asObservable();
  error$ = this.error.asObservable();

  ...

  signIn(params: SignInInput) {
    this.auth.next(false);

    this.signInMutation.mutate(params).subscribe(
      data => {
        ...
        this.auth.next(true);
        this.error.next(null);
      },

      error => {
        this.error.next('error message');
      }
    );
  }
} 

在 component.ts 中,我们可以订阅错误消息以执行一些特定操作,但这不是必须的。只在模板中使用 async 管道来显示简单消息就可以了:

export class AppComponent {
  error$ = this.authService.error$;
  ...

  constructor(...) {
    // manually subscribe to do some other tasks...
    // but it's not required to only display inside template (see below)
    this.error$.subscribe(error => {
      if (error) {

      }
    });

    // becareful to manage unsubscription (not done here to keep code simple)
  }  
}

在模板中,我们可以在发生错误时显示一条消息:

 <p *ngIf="error$ | async as error">{{ error }}</p>