Amigos, la forma del suscribe sale deprecado (aunque igual funciona), revisando la documentación se podría hacer así:
Http Basic
Lo que aprenderás para consumir API con Angular
Solicitudes GET
Detalle de producto
Implementando slides
Solicitudes POST
Solicitudes PUT y PATCH
Solicitudes DELETE
Url Parameters / Paginación
Observable vs. Promesa
Reintentar una petición
Buenas prácticas
El problema de CORS
Manejo de ambientes
Manejo de errores
Transformar peticiones
Evitando el callback hell
Auth
Login y manejo de Auth
Manejo de headers
Uso de interceptores
Enviar Token con un interceptor
Creando un contexto a interceptor
Archivos
Descarga de archivos con Http
Subida de archivos con Http
Despedida
Continúa con el Curso de Angular Router y Programación Modular
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
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.
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.
Presta atención a las siguientes importaciones desde RxJS y @angular/common/http
que utilizaremos.
// service/api.service.ts
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ApiService {
// ...
}
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.ts
getProduct(idProduct: number): Observable<Product> {
return this.http.get<Product>(`https://example.com/api/productos/${idProduct}`)
.pipe(
catchError((err: HttpErrorResponse) => {
return this.handleErrors(err)
})
);
}
handleErrors(error: HttpErrorResponse): Observable<never> {
if (error.status == HttpStatusCode.Forbidden)
return throwError('No tiene permisos para realizar la solicitud.');
if (error.status == HttpStatusCode.NotFound)
return throwError('El producto no existe.');
if (error.status == HttpStatusCode.InternalServerError)
return throwError('Error en el servidor.');
return throwError('Un error inesperado ha ocurrido.');
}
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.
En tu componente, captura los errores en las suscripciones de la siguiente manera:
// components/catalogo/catalogo.component.ts
this.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.
Contribución creada por: Kevin Fiorentino.
Aportes 22
Preguntas 2
Amigos, la forma del suscribe sale deprecado (aunque igual funciona), revisando la documentación se podría hacer así:
Otra forma de manipular los errores es:
observable.subcribe({
next: (resp) =>{},
error: (err) =>{},
complete: () =>{}
})
para angular a 9/9/2023 el service quedaria asi:
getProduct(id: string){
return this.http.get<Product>(`${this.URL_API}/${id}`)
.pipe(
catchError((err: HttpErrorResponse) => {
if(err.status === HttpStatusCode.NotFound){
return throwError(()=>'Product not found');
}
if(err.status === HttpStatusCode.Forbidden){
return throwError(()=>'You do not have permission to access this product');
}
if(err.status === HttpStatusCode.Unauthorized) {
return throwError(()=>'You must be logged in to access this product');
}
return throwError(()=>'An error has occurred, try again later');
})
);
}
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 😄
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) {
return this.http.get<Product>(`${this.apiUrl}/${id}`)
.pipe(
catchError((error: HttpErrorResponse) => {
switch(error.status) {
case HttpStatusCode.Conflict:
return throwError(() => new Error ('Ups algo esta fallando en el server'));
case HttpStatusCode.NotFound:
return throwError(() => new Error ('El producto no existe'));
default:
return throwError(() => new Error ('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
app.module.ts
import { SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2';
@NgModule({
imports: [
SweetAlert2Module
],
})
despues activamos el manejo de errores con sweetalert2 en nuestro products.component
products.component.ts
import Swal from 'sweetalert2';
export class ProductsComponent implements OnInit {
statusDetail: 'loading' | 'success' | 'error' | 'init' = 'init';
}
onShowDetail(id: number) {
this.statusDetail = 'loading';
this.productsService.getProduct(id)
.subscribe({
next: (d) => this.showDetailOk(d),
error: (e) => this.showDetailError(e),
complete: () => console.info('complete')
});
}
showDetailOk(data: Product) {
this.statusDetail = 'success';
console.log('producto obtenido: ', data);
this.toggleProductDetail();
this.productChosen = data;
}
showDetailError(e: any) {
this.statusDetail = 'error';
this.toggleProductDetail();
Swal.fire({
title: 'Error!',
text: e,
icon: 'error',
confirmButtonText: 'Ok',
});
}
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> {
return this.http.get<Product>(`${this.apiUrl}/${id}`)
.pipe(
catchError((err: HttpErrorResponse) => {
return this.handleErrors(err)
})
);
}
handleErrors(error: HttpErrorResponse): Observable<never> {
if (error.status == HttpStatusCode.Forbidden)
return throwError(() => new Error ('No tiene permisos para realizar la solicitud.'));
if (error.status == HttpStatusCode.NotFound)
return throwError(() => new Error ('El producto no existe.'));
if (error.status == HttpStatusCode.InternalServerError)
return throwError(() => new Error ('Error en el servidor.'));
return throwError(() => new Error ('Un error inesperado ha ocurrido.'));
}
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.component
onShowDetail(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'
}
})
}
// Products.services
getProductByPage(limit: number, offset: number): Observable<any> {
return this.http.get<IProduct[]>(this.url2, {
params: { limit, offset }
})
.pipe(
catchError((error: HttpErrorResponse) => {
switch (error.status) {
case HttpStatusCode.ServiceUnavailable:
return throwError(() => new Error("Service Unavailable"))
break;
case HttpStatusCode.NotFound:
return throwError(() => new Error("Product Not Found"))
break;
default:
return throwError(() => new Error("Error default"))
}
})
);
}
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:
return this.http.get<Product>(`${this.apiUrl}/${id}`)
.pipe(
catchError((response: HttpErrorResponse) => {
return {
[HttpStatusCode.NotFound] : throwError(() => new Error('No encontrado.')),
[HttpStatusCode.BadRequest] : throwError(() => new Error('Petición inválida.')),
[HttpStatusCode.InternalServerError] : throwError(() => new Error('El servidor no puede procesar la petición.'))
}[response.status] || throwError(() => new Error('No encontrado.'))
})
);
Sí, quieren usar una librería de notificaciones, una de las más populares es ngx-toastr:
npm install ngx-toastr --save
import { ToastrModule } from 'ngx-toastr';
// ....
@NgModule({
imports: [
BrowserModule,
FormsModule,
ToastrModule.forRoot()
],
})
export class AppModule { }
En el componente
import { ToastrService } from 'ngx-toastr';
export class MyComponent {
constructor(private toastr: ToastrService) {}
showSuccess() {
this.toastr.success('Operación exitosa', '¡Genial!');
}
}
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
export class MyComponent implements OnInit {
constructor(
private appService: AppService
) { }
ngOnInit(): void {
this.appService.toastr.warning('Producto no encontrado.', Constants.WARNING)
}
}
export class AppService {
constructor(
public toastr : ToastrService
) { }
Página principal:
https://codeseven.github.io/toastr/
Pueden ver un demo aquí:
https://codeseven.github.io/toastr/demo.html
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 valor
catchError((error) => { // maneja los errores que puedan ocurrir
console.error(error);
this.statusDetail = 'error';
return of(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 asignarla
this.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.
HttpStatusCode :
Angular incluye una interface para el manejo de los errores.
Comparando Angular con React, veo que Angular es mucho mas claro en sus conceptos y arquitectura lo que conlleva a un mejor enfoque y resultados a la hora de ser productivos y eficaces no padeciendo o muriendo en el intento como pasa con React.
Saludos y Exitos
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) {
case 500:
throw "Esto es un error 500";
break;
case 404:
throw "Esto es un error 404";
break;
default:
throw "Esto es un error default";
}
})
status request
Me funciono utilizando sweetalert2 😁
la variable statusDetail seria como spinner
En throwError no es necesario retornar un new Error('Nuestro error '), seria de la siguiente manera:
/**
* Manejo de errores
*/
erroreHttp(e: HttpErrorResponse) {
if (e.status === HttpStatusCode.NotFound) {
return throwError(() => ("Ups producto no esta encontrado"));
}
return throwError(() => "Ups algo esta fallando"));
}
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?