TypeScript - Angular'da yanıtlar nasıl ele alınır?
Bu temel şeylerle ilgili bir soru ama eylem sonucuna bağlı olarak bileşene nasıl geri dönüş yapılacağını bilmiyorum: success veya error, böylece onlarla ilgili işlemleri gerçekleştirebilirim.
Bir SignInComponent bileşenim var, bu bileşen formdan gerekli tüm bilgileri topluyor ve GraphQL isteklerimi yöneten AuthService'ime gönderiyor (şu anda yönlendirme ve diğer şeyler için). İstek başarılı olursa, şimdiye kadar her şey sorunsuz çalışıyor, ancak API'den herhangi bir hata yanıtı gelirse, bu hataları ve bunlarla ilgili bilgileri SignInComponent'imde bulundurmam gerekiyor. (yanlış bilgiler varsa kullanıcıyı bilgilendirmeliyim ve benzeri durumlar)
Değeri şöyle döndürmeye çalıştım:
signIn.subscribe({
next: // actions for successful response,
error: (err) => {return 'errors'} // from here
})
ve ayrıca bu şekilde bir Hata fırlatmayı denedim:
signIn.subscribe({
next: // actions for successful response,
error: (err) => {throw new Error('Oops')} // The Error
})
ve SignInComponent'ımda bunu bu şekilde yakalamaya çalıştım:
try {
this.authService.signIn(params);
} catch (e) {
// handling the error accordingly
}
Ama yukarıda bahsedilen tüm bunlar, SignInComponent'imde cevap almama ve sonuç durumu hakkında bilgilendirilmemi sağlamadı.
İşte benim kodum ve umarım birisi bana bu durumu iyi uygulamalar kullanarak nasıl ele alabileceğimi söyleyebilir.
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);
}
}
}
girişYapBileşeni
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;
}
}
Hepinize şimdiden teşekkürler!
Cevap #1
Yani bu, Angular sorunundan ziyade daha çok bir rxjs sorunu.
this.signInMutation.mutate(params), sunucu yanıtının sarmalanmış verilerini içeren bir Observable döndürür. Sizin durumunuzda, signin() metodunun bu observable üzerinde abone olması iyi bir fikir değildir.
Bunun yerine, yaygın bir uygulama gözlemlenebilir değeri hemen geri döndürmek olurdu:
signIn(params: SignInInput) {
return this.signInMutation.mutate(params);
}
Şimdi, aslında subscribe() bölümünüzde yaptığınız gibi bazı veri sonrası işleme çalışmaları yapmak isteyebileceğinizi söyleyebilirsiniz.
Tamam, onları bir pipe() içine taşıyalım
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);
}),
);
}
Tamam, şimdi bileşen kodunuza geri dönelim
onSubmit(e) {
// ...
this.authService.signIn(params).subscribe({
error: err => {
// handle your err here, where the handler belongs
this.errorMessage = 'oops, errors here! ' + err.message
}
})
}
Temel olarak, bu, Angular'da rxjs tabanlı uzak yanıtları ele almak için en yaygın uygulamalardan biridir. Elbette, bileşeniniz yok edildiğinde bu aboneliğe unsubscribe() gibi endişelenmeniz gereken birkaç diğer konu da vardır. Ama konudan sapmak istemiyorum.
Angular servislerinizin gözlemlenebilirleri doğrudan döndürmesi, bunları diğer servislerle veya reaktif formlar gibi rxjs tabanlı adaylarla paylaşmanıza ve daha fazla birleştirmenize de yardımcı olur.
Cevap #2
Zaten AuthService içinde bir auth boolean bayrağınız olduğundan, hata mesajıyla birlikte bir error bayrağı eklemeyi tercih edebilirsiniz.
Bileşenleriniz veya diğer servisler, mevcut durumu almak için buna abone olabilir.
Bu yaklaşım (bir durum yönetimi şekliyle), farklı durumları (kimlik doğrulama evet/hayır, hata/hiç hata yok...) almak için size daha fazla esneklik sağlar. Ayrıca kimlik doğrulama sürecinde evet olarak ayarlanan bir yükleniyor bayrağı da ekleyebilirsiniz.
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');
}
);
}
}
içinde component.ts, belirli işlemleri yapmak için hata mesajına abone olabiliriz, ancak bu gerekli değildir. Basit bir mesaj görüntülemek için şablon içinde sadece async pipe kullanmak yeterlidir :
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)
}
}
Şablonun içinde, hata durumunda bir mesaj gösterebiliriz:
<p *ngIf="error$ | async as error">{{ error }}</p>
