Reputation: 545
I have this setup: A user inputs an employee code, then I make an http call to an API that returns some extended data. If the code is incorrect, the API responds with a "Not Found" Error. I had the problem that the Observable just ended the subscription if the user types an incorrect code (as expected according to the specification), so I am using the retryWhen operator to handle the error and resubscribe, expecting the user to input a correct code.
The problem is that the logic inside retryWhen is only being executed for the first time an error happen. If the user fails two times, or even if the user fails once, then inputs a right code and then another time inputs a bad one, the code inside retryWhen is not reached.
export class EmployeeService {
constructor(private http: HttpClient) { }
getEmployee(code: string): Observable<Employee> {
const baseUrl = config.employeesUrl
return this.http.get<Employee>(`${baseUrl}/${code}`)
}
}
import { Component, OnInit } from "@angular/core";
import { Cart } from "src/app/interfaces/cart.interface";
import { fromEvent, timer } from "rxjs";
import { debounce, switchMap, retryWhen } from "rxjs/operators";
import { EmployeeService } from "../../services/employee.service";
import { AlertController } from "@ionic/angular";
@Component({
selector: "app-set-employee",
templateUrl: "./set-employee.page.html",
styleUrls: ["./set-employee.page.scss"]
})
export class SetEmployeePage implements OnInit {
constructor(
private employeeService: EmployeeService,
private alert: AlertController
) {}
cart: Cart = {
employee: { code: null, name: null },
transactions: null
};
async presentAlert() {
const alert = await this.alert.create({
header: "Error",
subHeader: "",
message: "Some Alert",
buttons: ["OK"]
});
await alert.present();
}
ngOnInit() {
const input = document.getElementById("codigo");
const $input = fromEvent(<HTMLInputElement>input, "keyup");
$input
.pipe(
debounce(() => timer(1000)),
switchMap(event =>
this.employeeService.getEmployee(event.target["value"])
),
retryWhen(error => {
console.log("retrying...");
this.cart.employee.name = null;
this.presentAlert();
return error;
})
)
.subscribe(
data => {
this.cart.employee = data;
},
error => console.log(error)
);
}
}
Upvotes: 1
Views: 612
Reputation: 14099
The code inside retryWhen
is only executed once on the first error. retryWhen(error$ => onError$)
will then resubscribe to the source Observable when the Observable returned inside retryWhen
(onError$
) emits. The error$
Observable retryWhen
receives as input emits whenever the source teminates with an error.
This means you have to add your error handling logic to the observable stream onError$
you return.
retryWhen(error$ => error$.pipe(
tap(() => {
console.log("retrying...");
this.cart.employee.name = null;
this.presentAlert();
})
))
retryWhen
in this scenario.Instead handle the error with catchError
inside getEmployee(code: string)
. Error handling should be done in the Service not the Component. I wrote an answer about it here: Why handle errors with catchError and not in the subscribe error callback in Angular.
Infact I would move the whole alert on error logic away from the Component into a Service.
Service
constructor(private http: HttpClient, private alert: AlertController) { }
getEmployee(code: string): Observable<Employee> {
const baseUrl = config.employeesUrl
return this.http.get<Employee>(`${baseUrl}/${code}`).pipe(
catchError(this.handleError('getEmployee', getAlertData(), {}))
)
}
private getAlertData() {
return {
header: "Error",
subHeader: "",
message: "Some Alert",
buttons: ["OK"]
};
}
private handleError<T> (operation = 'operation', alertData?: any, result?: T) {
return (error: any): Observable<T> => {
console.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return alertData
? from(this.alert.create(alertData)).pipe(
switchMap(alert => from(alert.present())),
switchMap(_ => of(result as T))
)
: of(result as T);
};
}
Component
private onDestroy$ = new Subject();
ngOnInit() {
fromEvent(<HTMLInputElement>input, "keyup").pipe(
debounce(() => timer(1000)),
switchMap(event => this.employeeService.getEmployee(event.target["value"])),
takeUntil(this.onDestroy$)
).subscribe(data => this.cart.employee = data);
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
Upvotes: 1
Reputation: 2330
You can use catchError
operator.
ngOnInit() {
const input = document.getElementById("codigo");
const $input = fromEvent(<HTMLInputElement>input, "keyup");
$input
.pipe(
debounce(() => timer(1000)),
switchMap(event =>
return this.employeeService.getEmployee(event.target["value"])
.pipe(catchError(e => {
return of(null)
})
))
)
.subscribe(
data => {
if (data === null) {
this.presentAlert();
} else {
this.cart.employee = data;
}
},
error => console.log(error)
);
}
https://stackblitz.com/edit/angular-iqnvxx?file=src/app/app.component.ts
Upvotes: 1