6. Servicios: lógica reutilizable y acceso a datos¶
El componente pide; el servicio sabe cómo resolver.
Generar un servicio¶
Genera student.service.ts y student.service.spec.ts. Sin HTML ni CSS: un servicio no representa interfaz.
Separar presentación de lógica y comunicación¶
| Componente — presenta y reacciona | Servicio — resuelve y reutiliza |
|---|---|
| Muestra datos y recibe acciones del usuario | Centraliza reglas y operaciones compartidas |
| Controla estados visuales: cargando, éxito y error | Comunica con la API mediante HttpClient |
| Solicita operaciones sin conocer URLs ni detalles HTTP | Devuelve datos tipados mediante Observables |
| Debe permanecer pequeño y fácil de entender | Puede inyectarse y reutilizarse en varios componentes |
El servicio¶
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { NewStudent, Student } from '../models/student.model';
@Injectable({ providedIn: 'root' })
export class StudentService {
private http = inject(HttpClient);
list(): Observable<Student[]> {
return this.http.get<Student[]>('/api/students');
}
create(student: NewStudent): Observable<Student> {
return this.http.post<Student>(
'/api/students', student
);
}
}
@Injectable({ providedIn: 'root' }): Angular crea una sola instancia para toda la aplicación y la entrega a quien la pida.inject(HttpClient): el servicio pide su propia dependencia.HttpClientdebe estar registrado conprovideHttpClient()enapp.config.ts(ver Proyecto base).get<Student[]>/post<Student>: el genérico indica el modelo que se espera en la respuesta.
El componente¶
import { Component, inject } from '@angular/core';
import { Student } from '../../models/student.model';
import { StudentService } from '../../services/student.service';
import { StudentCardComponent } from '../student-card/student-card.component';
@Component({
selector: 'app-student-list',
standalone: true,
imports: [StudentCardComponent],
template: `
@for (s of students; track s.id) {
<app-student-card [student]="s" />
}
`
})
export class StudentListComponent {
private service = inject(StudentService);
students: Student[] = [];
ngOnInit(): void {
this.load();
}
load(): void {
this.service.list()
.subscribe(data => this.students = data);
}
}
→ El componente delega la operación y se concentra en la experiencia del usuario.
Observables en lo justo¶
HttpClient no devuelve los datos directamente, sino un Observable: una promesa de que la respuesta llegará más adelante.
sequenceDiagram
participant C as StudentListComponent
participant S as StudentService
participant A as API /api/students
C->>S: list()
S-->>C: Observable<Student[]>
C->>C: subscribe(...)
Note over C,A: solo ahora sale la petición
C->>A: GET /api/students
A-->>C: JSON → data
C->>C: this.students = data
Sin subscribe no hay petición
this.service.create(student); no envía nada. La petición HTTP sale solo cuando alguien se suscribe.
Inyección de dependencias¶
El componente no crea el servicio con new: lo pide con inject(). Es el mismo principio de Spring:
| Spring | Angular |
|---|---|
@Service |
@Injectable({ providedIn: 'root' }) |
Inyección por constructor / @Autowired |
inject(StudentService) o constructor |
| Bean singleton | Servicio root singleton |
¿Por qué no new StudentService()?
- Todos los componentes comparten la misma instancia.
- Angular resuelve la cadena de dependencias (
StudentService→HttpClient→ …). - En las pruebas se puede reemplazar por un servicio falso sin tocar el componente.
Correspondencia con el backend¶
| Angular | HTTP | Spring (Controller) |
|---|---|---|
http.get<Student[]>('/api/students') |
GET /api/students |
@GetMapping |
http.get<Student>('/api/students/42') |
GET /api/students/42 |
@GetMapping("/{id}") |
http.post<Student>('/api/students', body) |
POST + JSON |
@PostMapping + @RequestBody |
http.put<Student>('/api/students/42', body) |
PUT + JSON |
@PutMapping("/{id}") |
http.delete<void>('/api/students/42') |
DELETE |
@DeleteMapping("/{id}") |
¿Y si el frontend y el backend corren en puertos distintos?
En desarrollo Angular corre en localhost:4200 y Spring en localhost:8080. Para que /api/students llegue al backend, se configura un proxy del servidor de desarrollo:
La alternativa es habilitar CORS en Spring para el origen http://localhost:4200.
Idea clave
Si dos componentes necesitan la misma lógica, probablemente esa lógica pertenece a un servicio.
Siguiente: Routing.