¿Qué es Angular?
Angular es un framework de aplicaciones completo mantenido por Google. Mientras que React es una librería alrededor de la cual ensamblas un stack, Angular ya incluye el stack: componentes, routing, formularios, HTTP, inyección de dependencias, testing y una potente CLI, todo diseñado para trabajar en conjunto. Está centrado en TypeScript y es opinionated, que es precisamente la razón por la cual los equipos grandes lo eligen.
Esa completitud es la principal contrapartida. Aceptas una curva de aprendizaje más pronunciada y más convenciones del framework a cambio de consistencia, herramientas sólidas y menos decisiones arquitectónicas que tomar desde cero. Para un prototipo pequeño, esto puede sentirse pesado; para una base de código empresarial de cinco años, suele ser un alivio.
Componentes y plantillas
Un componente es una clase de TypeScript decorada con @Component, vinculada a una plantilla HTML y estilos opcionales.
// user-card.component.ts
import { Component, Input } from "@angular/core";
@Component({
selector: "app-user-card",
standalone: true,
template: `
<article class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</article>
`,
})
export class UserCardComponent {
@Input({ required: true }) user!: { name: string; email: string };
}
La plantilla utiliza {{ }} para la interpolación y directivas para la lógica. El Angular moderno utiliza bloques de flujo de control integrados en lugar de las antiguas directivas estructurales.
<!-- list.html -->
@if (users.length > 0) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
} @else {
<p>No users yet.</p>
}
La expresión track en @for cumple la misma función que una key en React o Vue: le indica a Angular cómo emparejar los elementos entre actualizaciones.
Data binding
Las plantillas de Angular admiten cuatro tipos de binding.
- Interpolation
{{ value }}renderiza un valor como texto. - Property binding
[src]="url"asigna una propiedad de un elemento a partir de una expresión. - Event binding
(click)="save()"llama a un método cuando se dispara un evento. - Two-way binding
[(ngModel)]="name"combina ambos para los inputs de formularios.
<!-- bindings.html -->
<img [src]="user.avatar" [alt]="user.name" />
<button (click)="save()">Save</button>
<input [(ngModel)]="query" />
El two-way binding es conveniente para formularios sencillos, pero para cualquier cosa que requiera validación, los reactive forms suelen ser una mejor opción.
Signals
Los Signals son la primitiva de reactividad moderna de Angular. Un signal contiene un valor y notifica a los consumidores interesados cuando este cambia, lo que permite que el framework actualice únicamente las partes de la vista que dependen de él.
// cart.component.ts
import { Component, signal, computed } from "@angular/core";
@Component({
selector: "app-cart",
standalone: true,
template: `<p>Total: {{ total() }}</p>`,
})
export class CartComponent {
items = signal([{ price: 10 }, { price: 20 }]);
total = computed(() =>
this.items().reduce((sum, i) => sum + i.price, 0),
);
add(price: number) {
this.items.update((items) => [...items, { price }]);
}
}
computed deriva un valor de otros signals y lo almacena en caché hasta que una dependencia cambia. effect ejecuta un efecto secundario cuando cambian los signals que lee. Juntos, cubren la mayor parte de las necesidades de los componentes.
RxJS y observables
Angular tiene una integración profunda con RxJS. Un observable representa un flujo de valores a lo largo del tiempo, y los operadores permiten transformar y combinar dichos flujos.
// search.component.ts
import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs";
@Component({ /* ... */ })
export class SearchComponent {
private query$ = new Subject<string>();
results = toSignal(
this.query$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((q) => this.api.search(q)),
),
{ initialValue: [] },
);
}
Aquí es donde RxJS demuestra su valor: el debouncing, la cancelación de peticiones obsoletas y la combinación de flujos son difíciles de expresar con promesas simples. Para estados sincrónicos sencillos, los signals son más simples. El Angular moderno permite mezclar ambos y convertirlos mediante toSignal y toObservable.
Servicios e inyección de dependencias
La lógica y los datos compartidos pertenecen a los servicios, no a los componentes. La inyección de dependencias los suministra donde sea necesario.
// users.service.ts
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable({ providedIn: "root" })
export class UsersService {
private http = inject(HttpClient);
getAll() {
return this.http.get<User[]>("/api/users");
}
}
// users.component.ts
export class UsersComponent {
private users = inject(UsersService);
list = toSignal(this.users.getAll(), { initialValue: [] });
}
providedIn: "root" registra un singleton para toda la aplicación. La DI facilita el intercambio de servicios en las pruebas y mantiene los componentes enfocados en la presentación.
Routing
El Router de Angular mapea URLs a componentes y soporta rutas anidadas, guards, resolvers y lazy loading.
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{ path: "", component: HomeComponent },
{ path: "users/:id", component: UserProfileComponent },
{
path: "admin",
loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
];
La funcionalidad de lazy loading mantiene el bundle inicial pequeño, y los guards te permiten bloquear la navegación hasta que se cumpla una condición, como la autenticación.
Formularios y HTTP
Angular tiene dos enfoques para manejar formularios. Los Reactive forms definen el modelo y los validadores en la clase, lo que permite escalar mejor y facilita las pruebas. Los Template-driven forms utilizan directivas como ngModel y son más rápidos para casos sencillos.
// signup.component.ts
form = new FormGroup({
email: new FormControl("", [Validators.required, Validators.email]),
password: new FormControl("", [Validators.required, Validators.minLength(8)]),
});
submit() {
if (this.form.invalid) return;
this.http.post("/api/signup", this.form.getRawValue()).subscribe();
}
HttpClient devuelve observables, por lo que las peticiones se pueden componer con operadores de RxJS para gestionar reintentos, cancelaciones y el manejo de errores.
Mejores prácticas
- Prioriza el uso de standalone components y signals para el código nuevo.
- Mantén los componentes enfocados en la presentación y traslada la lógica a los servicios.
- Utiliza reactive forms para cualquier funcionalidad que requiera validación o campos dinámicos.
- Implementa lazy-load en las áreas de funcionalidades para mantener el bundle inicial reducido.
- Utiliza la detección de cambios
OnPusho signals para limitar las comprobaciones innecesarias. - Rastrea los elementos en
@formediante un valor único y estable. - Cancela la suscripción a los observables de larga duración, o utiliza el async pipe y
toSignal.
Errores comunes
- Colocar la lógica de negocio en los componentes en lugar de en los servicios.
- Suscribirse manualmente y olvidar cancelar la suscripción, lo que provoca fugas de memoria.
- Recurrir a RxJS cuando un signal sería más sencillo.
- Mutar el valor de un signal directamente en lugar de usar
setoupdate. - Crear un único NgModule enorme en lugar de rutas de funcionalidades con lazy-loading.
- Tratar a Angular como si fuera React y luchar contra la estructura del framework.
Próximos pasos
Angular recompensa la paciencia con una estructura que se mantiene sólida a lo largo de los años. Profundiza en TypeScript, comprende cómo se compara con React y Vue, y aprende sobre las herramientas de Node.js que impulsan su CLI. Después, construye una funcionalidad que incluya routing, un servicio y un formulario reactivo para ver cómo encajan todas las piezas.