No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Manejo de errores

13/23
Recursos

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.

// 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 {
    // ...
}

2. Manejo de errores con catchError()

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.');
}

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.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

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Para no mostrar una alerta podríamos utilizar aviso de una librería llamada SweetAlert2.

Debemos instalar

Lo importamos dentro del componente

Dentro del método lo activamos

Hay varios tipos de alertas que podemos utilizar y customizar dentro de la documentación de la librería lo informa.

https://sweetalert2.github.io/#examples

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.'));
  }

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.

https://http.cat

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

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;},    )  }`

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.

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";
        }
      })

https://rxjs.dev/api/operators/catchError

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"));
  }