Las peticiones HTTP que tu aplicación realiza pueden fallar por una u otra razón. Recuerda que una aplicación profesional tiene que contemplar estos escenarios y estar preparada para cuando esto suceda.
Manejo de Errores con RxJS
Ya hemos visto anteriormente la función retry() de RxJS para reintentar una petición en caso de fallo. Pero si el error persiste, veamos cómo es posible manejar los mismos.
1. Imports desde RxJS
Presta atención a las siguientes importaciones desde RxJS y @angular/common/http que utilizaremos.
Agrega a los métodos que realizan las solicitudes HTTP un .pipe() para manipular los datos que el observable emita antes de enviarlos al componente.
Aquí estamos utilizando catchError de RxJS para capturar los errores y le pasamos un método personalizado llamado handleErrors() para centralizar el manejo de los mismos (Así no repetimos el mismo código en todos los pipes).
// service/api.service.tsgetProduct(idProduct: number):Observable<Product>{returnthis.http.get<Product>(`https://example.com/api/productos/${idProduct}`).pipe(catchError((err:HttpErrorResponse)=>{returnthis.handleErrors(err)}));}handleErrors(error:HttpErrorResponse):Observable<never>{if(error.status==HttpStatusCode.Forbidden)returnthrowError('No tiene permisos para realizar la solicitud.');if(error.status==HttpStatusCode.NotFound)returnthrowError('El producto no existe.');if(error.status==HttpStatusCode.InternalServerError)returnthrowError('Error en el servidor.');returnthrowError('Un error inesperado ha ocurrido.');}
3. Identifica el tipo de error
Dicho método recibe un parámetro del tipo HttpErrorResponse que Angular nos provee para tipar errores HTTP. Utilizando el enumerado HttpStatusCode puedes determinar qué tipo de error se produjo. Si fue un error 403, 404, 500, etc. Fácilmente, puedes verificarlo y devolver un error al componente con throwError y un mensaje personalizado.
NOTA: Más allá de este enum que Angular nos facilita, es muy importante que como desarrollador de software sepas diferenciar lo que significa un 401, 403, 404, 500, 501, entre otros estados HTTP. Poco a poco lo irás aprendiendo.
4. Captura los errores en los componentes
En tu componente, captura los errores en las suscripciones de la siguiente manera:
// components/catalogo/catalogo.component.tsthis.apiService.getProducts().subscribe(res=>{this.productos= res;},err=>{alert(err);// Aquí se emitirá el alerta con el mensaje que `throwError` devuelva.});
Así, ya puedes diferenciar los errores y comunicarle al usuario si algo salió mal de la manera más apropiada, mostrándole un mensaje o una alerta.
getProduct(id: string){returnthis.http.get<Product>(`${this.URL_API}/${id}`).pipe(catchError((err:HttpErrorResponse)=>{if(err.status===HttpStatusCode.NotFound){returnthrowError(()=>'Product not found');}if(err.status===HttpStatusCode.Forbidden){returnthrowError(()=>'You do not have permission to access this product');}if(err.status===HttpStatusCode.Unauthorized){returnthrowError(()=>'You must be logged in to access this product');}returnthrowError(()=>'An error has occurred, try again later');}));}
Crack es la manera más actualizada para hacerlo con rxjs
Y yo creando la interfáz para el error, y para los estados validando el numerito, teniendo tantas cosas en Angular para ello. Que gran clase. Gracias :D
Excelente clase! Poco manejo los errores, por lo que esta clase me encanto y tengo mejor manejo de los mismos. Caso curioso de acuerdo a mi experiencia, pocos manejan los errores, es común que eso lo haga el backend. Ya hoy por hoy porque se la codificación de los errores y los puedo solucionar en menor tiempo, pero ciertamente, como Front-End si estamos dedicados a desarrollar del lado del cliente, debemos manejar estos errores para tener un desarrollo más limpio y una mejor administración de los errores para la pronta solución de los mismos.
Con el manejo de observables debemos ver como es el manejo de errores
Se puede obtener un manejo de errores desde el servicio para el manejo de excepciones en el backend
product.service.ts
import{HttpClient,HttpParams,HttpErrorResponse,HttpStatusCode}from'@angular/common/http';import{ retry, catchError }from'rxjs/operators';import{ throwError }from'rxjs';getProduct(id:number){returnthis.http.get<Product>(`${this.apiUrl}/${id}`).pipe(catchError((error:HttpErrorResponse)=>{switch(error.status){caseHttpStatusCode.Conflict:returnthrowError(()=>newError('Ups algo esta fallando en el server'));caseHttpStatusCode.NotFound:returnthrowError(()=>newError('El producto no existe'));default:returnthrowError(()=>newError('Ups algo salio mal'));}}))}
Tambien se puede hacer manejo de errores desde el front end, utilizano la libreria sweetalert2
sweetalert2
sweetalert2 Github
Lo primero es importar la sweetalert2 en nuestro app.module.ts
Hola, les dejo el código del handlerErrors implementado, tomado de los recursos de esta clase y la solución al deprecated del trhowError.
getProduct(id: string):Observable<Product>{returnthis.http.get<Product>(`${this.apiUrl}/${id}`).pipe(catchError((err:HttpErrorResponse)=>{returnthis.handleErrors(err)}));}handleErrors(error:HttpErrorResponse):Observable<never>{if(error.status==HttpStatusCode.Forbidden)returnthrowError(()=>newError('No tiene permisos para realizar la solicitud.'));if(error.status==HttpStatusCode.NotFound)returnthrowError(()=>newError('El producto no existe.'));if(error.status==HttpStatusCode.InternalServerError)returnthrowError(()=>newError('Error en el servidor.'));returnthrowError(()=>newError('Un error inesperado ha ocurrido.'));}
El metodo throwError(), según he visto en la documentación, puede ser sustituido por la palabra reservada throw. A mi me funciona:
catchError((error:HttpErrorResponse)=>{switch(error.status){case500:throw"Esto es un error 500";break;case404:throw"Esto es un error 404";break;default:throw"Esto es un error default";}})
Incluso el uso throwError como muestra el video ya está deprecado (aún que todavia funciona). Lo correcto es:
returnthrowError(()=>newError('Esto es un error'));
me gusto esta clase !! flujo de errores,
Por azares de la vida me toca aprender Angular a pesar de que soy team ReactJS y lo que he podido ver es que esta ruta es excelente tiene todo lo necesario para realizar features complejas, aplicando clean code, error handling, SOLID y otros conceptos muy importantes. Me encantaría que la ruta de React fuera así. Concreta, clara, organizada y con testing.
Han surgido varios cambios, aquí les dejo mi código por si les sirve de guía
// Products.componentonShowDetail(id: number){this.statusDetail='loading'this.productsService.getProduct(id).subscribe({next:(data)=>{this.toggleDetail()this.productDetail= data
this.statusDetail='success'},error:(err)=>{console.log(err.error.message)this.statusDetail='error'}})}
Para los que no sepan sobre los codigos HTTP les dejo la siguiente url que brinda un poco de humor con los tipos de codigos que hay.
Para el manejo de errores:
returnthis.http.get<Product>(`${this.apiUrl}/${id}`).pipe(catchError((response:HttpErrorResponse)=>{return{[HttpStatusCode.NotFound]:throwError(()=>newError('No encontrado.')),[HttpStatusCode.BadRequest]:throwError(()=>newError('Petición inválida.')),[HttpStatusCode.InternalServerError]:throwError(()=>newError('El servidor no puede procesar la petición.'))}[response.status]||throwError(()=>newError('No encontrado.'))}));
Es mejor tener el manejo de errores en el componente? en el servicio? en ambos?
Por lo que veo cuando pongo la parte de manejo de errores despues del subscribe, me figura como que subscribe esta deprecado, alguien me podria indicar porque pasa esto ?
Wow, eso es porque tú estás trabajando con la versión 13 de Angular que trae una nueva versión de Angular, pero no debes preocuparte, entonces si quieres manejar el error esta sería la nueva forma:
import{of}from'rxjs';// recommended of([1,2,3]).subscribe((v)=>console.info(v));// also recommendedof([1,2,3]).subscribe({next:(v)=>console.log(v),error:(e)=>console.error(e),})
Entonces tu primera función va en next y la segunda función va en error
Muchas gracias @nicobytes. Puede ser que haya visto que esta forma que utilizas tambien funciona en proyectos de angular 11 por ejemplo?
Sí, quieren usar una librería de notificaciones, una de las más populares es ngx-toastr:
Amigo hice todo cuanto indicaste y no he logrado hacer que funcione las notificaciones en este proyecto. Podrías compartir tu repositorio por favor
my-store-angular aquí está mi repositorio completo espero que te sea de ayuda
Para los que no les gusta sweet alert 2 como a mi (es molesto), existe Toast, es menos molesto y se ve cool:
https://www.npmjs.com/package/toastr
exportclassMyComponentimplementsOnInit{constructor(privateappService:AppService){}ngOnInit():void{this.appService.toastr.warning('Producto no encontrado.',Constants.WARNING)}}exportclassAppService{constructor(publictoastr:ToastrService){}
Amigo hice todo cuanto indicaste y no he logrado hacer que funcione las notificaciones en este proyecto. Podrías compartir tu repositorio por favor
Costo bastante pero creo que así quedó;
onShowDetail(id: number){ this.productsService.getProduct(id) .pipe( catchError((error) => { // Maneja los errores que puedan ocurrir (puedes agregar lógica de manejo de errores aquí) console.error(error.statusText); // Remite error if (error.status === 400){ return throwError(()=>'El producto no existe') } if (error.status === 500){ return throwError(()=>'Error en el servidor') } return throwError(() => 'Ups, algo salió mal') })) .subscribe(data => { this.toggleDetail(); this.productChosen = data;}, ) }
La siguiente es una forma más actualizada y completa de hacer la función onShowDetail según la documentación de RxJS.
onShowProductDetail(id: string){this.statusDetail='loading';this.productsService.getProduct(id).pipe(tap(()=>this.toggleProductDetail()),// realiza una acción cuando se emite un valorcatchError((error)=>{// maneja los errores que puedan ocurrirconsole.error(error);this.statusDetail='error';returnof(null);// retorna un observable con un valor nulo para que el flujo continúe})).subscribe((data)=>{if(data){// verifica que la respuesta sea válida antes de asignarlathis.productChosen= data;this.statusDetail='success';}});}
pipe: Es un método que se utiliza para encadenar múltiples operadores de RxJS y crear una cadena de transformaciones de datos. El método pipe toma como argumento una serie de operadores separados por comas, los cuales se aplican secuencialmente a los datos que fluyen a través del observable. Al utilizar pipe, se crea una nueva instancia del observable con las transformaciones aplicadas.
tap: Es un operador de RxJS que permite realizar una acción secundaria en los datos que fluyen a través del observable, sin modificar los datos. tap es útil para depurar y monitorear el flujo de datos, ya que permite imprimir mensajes en la consola o realizar otras acciones secundarias mientras se procesan los datos.
of: Es una función que se utiliza para crear un observable que emite un valor específico y luego se completa. of es útil para crear observables que emiten valores estáticos o constantes, y es comúnmente utilizado en combinación con otros operadores para crear flujos de datos más complejos.