¿Qué son los servicios en Angular?

Un servicio es una clase diseñada para encapsular lógica o funcionalidad que debe ser compartida entre diferentes partes de la aplicación. Se utiliza principalmente para:

  1. Separar lógica de negocio del componente.
  2. Compartir datos y métodos entre componentes.
  3. Facilitar pruebas unitarias al desacoplar funcionalidades.
  4. Gestionar recursos externos como APIs o almacenamiento local.

Angular proporciona el mecanismo de inyección de dependencias (DI) para gestionar servicios, lo que garantiza que siempre se trabaje con una única instancia compartida (Patrón Singleton) a menos que se configure de otra manera.


Cómo se implementa un servicio

Crear e implementar un servicio es sencillo gracias al Cliente Angular y al patrón de inyección de dependencias. Veamos los pasos necesarios para realizar esta implementación.

1. Crear un servicio

Utiliza el comando de Angular CLI para generar un servicio:

ng generate service nombre-del-servicio

Este comando genera dos archivos:

  • nombre-del-servicio.service.ts (código del servicio).
  • nombre-del-servicio.service.spec.ts (archivo de pruebas).

El archivo generado podría verse así:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root' // Proveedor en el nivel raíz
})
export class NombreDelServicio {
  constructor() { }
}

2. Proveer el servicio

Angular registra automáticamente el servicio en el nivel raíz mediante el decorador @Injectable({ providedIn: 'root' }). Esto significa que estará disponible en toda la aplicación como un Singleton.

Si necesitas limitar el alcance del servicio, puedes especificarlo manualmente en los providers de un módulo o componente.

En Angular, además del nivel raíz, podemos registrar el servicio a nivel de módulo o de componente.


Casos de uso de los servicios

1. Lógica de negocio compartida

Por ejemplo, nuestra aplicación de adopción de mascotas puede necesitar filtrar o procesar datos sobre mascotas disponibles:

export class MascotaService {
  filtrarPorEdad(mascotas: Mascota[], edad: number): Mascota[] {
    return mascotas.filter(m => m.edad === edad);
  }
}

2. Gestión de estados o datos globales

Los servicios permiten compartir datos entre componentes sin necesidad de usar un patrón de emisión de eventos.

export class SesionService {
  private usuarioLogeado: Usuario | null = null;

  setUsuario(usuario: Usuario): void {
    this.usuarioLogeado = usuario;
  }

  getUsuario(): Usuario | null {
    return this.usuarioLogeado;
  }
}

Para gestión de estados simples, usar servicios es posible y puede ser una solución aplicable.

3. Consumo de APIs

Este es uno de los casos más comunes. Un servicio interactúa con un backend para enviar o recibir datos.

Vamos a entrar en detalle para este caso de aplicación.


Uso de servicios con consumos de APIs

Para consumir APIs en Angular, utilizamos el módulo HttpClientModule y las herramientas de RxJS. A continuación, veamos cómo integrarlos.

1. Configuración inicial

Primero, asegúrate de importar el módulo HttpClientModule en el archivo principal del módulo de tu aplicación (app.module.ts):

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule // Importar aquí
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

2. Crear el servicio para la API

Supongamos que estamos construyendo una aplicación de adopción de mascotas y queremos consumir una API que proporciona una lista de mascotas. El servicio podría verse así:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

export interface Mascota {
  id: number;
  nombre: string;
  edad: number;
  tipo: string;
}

@Injectable({
  providedIn: 'root' // Disponible en toda la aplicación
})
export class MascotaService {
  private apiUrl = 'http:/localhost:5000/mascotas';

  constructor(private http: HttpClient) {}

  // Obtener la lista de mascotas
  getMascotas(): Observable<Mascota[]> {
    return this.http.get<Mascota[]>(this.apiUrl).pipe(
      map(data => data), // Transforma los datos si es necesario
      catchError(this.handleError) // Manejo de errores
    );
  }

  // Manejo de errores
  private handleError(error: HttpErrorResponse) {
    let mensaje = 'Ocurrió un error inesperado.';
    if (error.error instanceof ErrorEvent) {
      // Error del cliente
      mensaje = `Error del cliente: ${error.error.message}`;
    } else {
      // Error del servidor
      mensaje = `Error del servidor: ${error.status}, mensaje: ${error.message}`;
    }
    return throwError(() => mensaje);
  }
}

3. Consumir el servicio en un componente

Ahora que tenemos el servicio, podemos utilizarlo en un componente para mostrar la lista de mascotas.

import { Component, OnInit } from '@angular/core';
import { MascotaService, Mascota } from './mascota.service';

@Component({
  selector: 'app-lista-mascotas',
  template: `
    <div *ngIf="mascotas; else cargando">
      <ul>
        <li *ngFor="let mascota of mascotas">{{ mascota.nombre }} - {{ mascota.tipo }}</li>
      </ul>
    </div>
    <ng-template #cargando>
      <p>Cargando mascotas...</p>
    </ng-template>
  `
})
export class ListaMascotasComponent implements OnInit {
  mascotas: Mascota[] | null = null;

  constructor(private mascotaService: MascotaService) {}

  ngOnInit() {
    this.mascotaService.getMascotas().subscribe({
      next: (data) => (this.mascotas = data),
      error: (error) => console.error('Error al cargar mascotas:', error),
    });
  }
}

4. Ventajas de usar Observables

Al usar Observables en Angular:

  • Puedes cancelar suscripciones automáticamente con operadores como takeUntil.
  • Es posible combinar flujos de datos con operadores como combineLatest.
  • Tienes más control sobre el manejo de errores y la transformación de datos.

Los servicios son una herramienta fundamental en Angular para estructurar aplicaciones bien organizadas y escalables. Al integrarlos con HttpClient y RxJS, puedes manejar flujos de datos asíncronos de manera eficiente, aprovechar los operadores para transformar datos y garantizar una mejor experiencia del usuario.

Practicar la implementación de servicios con APIs reales es una excelente manera de dominar este concepto y sacarle el máximo provecho a Angular.

Te dejo un video donde de forma práctica verificamos los conceptos que hemos revisado anteriormente.