Angular 21 de Cero a Experto / Servicios y DI
Servicios e inyección de dependencias: lógica compartida
Los componentes pintan la UI, pero ¿dónde vive la lógica que comparten? ¿Dónde guardas los gastos para que la lista, el resumen y el formulario vean lo MISMO? La respuesta de Angular es el SERVICIO: una clase con lógica o estado, que Angular crea una vez y ENTREGA a quien la pida mediante inyección de dependencias. Es una de las señas de identidad del framework y la base de las apps grandes. Hoy entiendes qué es un servicio, qué es la DI y por qué es tan poderosa.
Pedir, no fabricar
Qué es un servicio
Un servicio es una clase normal decorada con @Injectable. Ahí pones lo que no pertenece a
un componente concreto: acceso a datos, lógica de negocio, estado compartido, integración
con APIs. Un componente "flaco" (solo UI) y servicios "gordos" (lógica) es la arquitectura
que escala.
import { Injectable, signal, computed } from '@angular/core';
export interface Gasto { id: number; nombre: string; valor: number; }
@Injectable({ providedIn: 'root' }) // Angular lo crea una vez, para toda la app
export class BolsilloService {
private readonly _gastos = signal<Gasto[]>([]);
// expone lecturas (señales de solo lectura) y acciones (métodos)
readonly gastos = this._gastos.asReadonly();
readonly total = computed(() => this._gastos().reduce((s, g) => s + g.valor, 0));
agregar(nombre: string, valor: number) {
const id = Date.now();
this._gastos.update((gs) => [...gs, { id, nombre, valor }]);
}
eliminar(id: number) {
this._gastos.update((gs) => gs.filter((g) => g.id !== id));
}
}Fíjate: el servicio guarda el estado en una señal privada y expone una versión de solo
lectura (asReadonly()) más un computed para el total. Los componentes leen y disparan
acciones, pero no pueden mutar el estado directamente. Es encapsulación —lo que aprendiste
con _privado y @property en Python, aplicado aquí—.
providedIn: 'root' = un singleton para toda la app
@Injectable({ providedIn: 'root' }) le dice a Angular: "crea UNA instancia de este servicio
para toda la aplicación y entrégala a quien la pida". Ese único objeto compartido es lo que
hace que la lista, el resumen y el formulario vean los mismos gastos: todos reciben la misma
instancia de BolsilloService.
Usar el servicio desde un componente
El componente lo PIDE con la función inject() (la verás a fondo en la próxima lección) y
luego usa sus señales y métodos:
import { Component, inject } from '@angular/core';
import { BolsilloService } from './bolsillo.service';
@Component({
selector: 'app-resumen',
template: `
<p>{{ bolsillo.gastos().length }} gastos</p>
<p>Total: {{ bolsillo.total() }} COP</p>
`,
})
export class ResumenComponent {
protected readonly bolsillo = inject(BolsilloService); // pedir la instancia
}Otro componente —un FormularioComponent— puede inyectar el MISMO servicio y llamar
bolsillo.agregar(...). Como es un singleton, el ResumenComponent verá el nuevo total al
instante, porque total es un computed sobre la señal compartida. Comunicación entre
componentes lejanos, sin pasar nada por props.
El puente desde React
Si vienes de React, un servicio con señales cumple el papel que allá repartías entre varias herramientas:
// En React necesitabas combinar piezas para lo mismo:
// - Context + Provider → para compartir estado sin prop drilling
// - un custom hook useX() → para encapsular la lógica
// - Zustand/Redux → para un store global
// En Angular, un servicio con signals cubre los tres roles:
// @Injectable({ providedIn: 'root' }) → singleton global (como el store)
// signals/computed dentro → estado reactivo encapsulado
// inject(Servicio) → acceso desde cualquier componente¿Qué garantiza `@Injectable({ providedIn: 'root' })`?
Mini-reto
Diseña (en tu editor) un PresupuestoService con providedIn: 'root': 1) una señal privada
_limite con valor inicial 500000 y su versión readonly; 2) un método cambiarLimite(v)
que la actualice; 3) un computed restante que dependa de _limite y del total de otro
servicio inyectado. Piensa qué componentes lo pedirían y por qué NO necesitas pasar el límite
por props.
Qué sigue
Ya sabes qué es un servicio y por qué la DI comparte estado sin esfuerzo. Usaste inject()
sin explicarlo. La próxima lección lo desmenuza: qué es exactamente inject(), cómo
reemplazó a la inyección por constructor, y por qué es más flexible. El mecanismo que
conecta todo el sistema de dependencias de Angular.