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>
