Ir al contenido

Angular

Antes de entender qué es Angular, es más útil entender qué problema vino a resolver. Cuando una aplicación web es pequeña, una página con tres botones, JavaScript vanilla es suficiente. El problema aparece cuando la aplicación crece.

En JavaScript puro, el HTML que el usuario ve vive en el DOM. El estado de la aplicación (datos que el código conoce) vive en variables. No hay ningún mecanismo automático que los mantenga sincronizados.

Cada vez que el estado cambia, el desarrollador debe recordar qué parte del DOM reflejar ese cambio y escribir el código imperativo para hacerlo. Esto funciona, pero no escala.

⚠️ Vanilla JS, código imperativo

// El estado vive aquí
let contador = 0;
// El DOM vive en otro lugar
const btn = document.querySelector('#btn');
const span = document.querySelector('#valor');
// El desarrollador sincroniza manualmente
btn.addEventListener('click', () => {
contador++;
span.textContent = contador; // ← obligatorio: sin esta línea el valor del contador cambia pero el usuario sigue viendo el anterior
});

Con 3 variables y 2 elementos funciona. Con 50 variables y 200 elementos, olvidar una sincronización produce bugs silenciosos.

✅ Angular, declarativo

// El estado vive en la clase
contador = 0;
// El template declara la relación
// <span>{{ contador }}</span>
// <button (click)="contador++">
// Angular se encarga de la sincronización.
// El desarrollador solo describe
// qué debe mostrarse, no cómo hacerlo.

La cantidad de elementos en pantalla puede crecer sin agregar complejidad a la sincronización.

Actividad interactiva: Comparación imperativo vs declarativo

Sección titulada «Actividad interactiva: Comparación imperativo vs declarativo»

Aquí está el contraste en acción. Nota cómo en el lado vanilla el código sincroniza manualmente, mientras que en Angular el framework sincroniza automáticamente.

⚖️ Imperativo vs Declarativo

📝 Vanilla JS (Imperativo)

<!-- HTML -->
<button id="btn">Incrementar</button>
<p>Resultado: <span id="val">0</span></p>

// TypeScript
const btn = document.querySelector('#btn');
const val = document.querySelector('#val');
let contador = 0;

btn.addEventListener('click', () => {
  contador++;
  val.textContent = contador;
});
Variable contador
0
Lo que ve el usuario
0

⚠️ El HTML solo pone id; la conexión se arma después, en el TypeScript. Apaga la línea y sigue haciendo clic: la variable cambia, la pantalla no. Mantenerlas iguales es trabajo tuyo.

✨ Angular (Declarativo)

<!-- Template -->
<button (click)="contador++">
  Incrementar
</button>

<p>Resultado: {{ contador }}</p>

// TypeScript
contador = 0;
Estado contador
0
Lo que ve el usuario
0

Aquí no hay interruptor: no existe esa línea.

✅ La conexión se declara en el propio HTML: (click) dice qué pasa al hacer clic y {{ }} dice qué mostrar. No queda ninguna línea de sincronización que puedas olvidar.

💡 Concepto: los dos botones dicen "Incrementar" y hacen lo mismo. La diferencia está en dónde vive la conexión, lo resaltado en azul. En vanilla el HTML es inerte: solo aportaid para que el TypeScript encuentre los elementos y los conecte a mano. En Angular la conexión se declara en el mismo HTML, y por eso no queda una línea de sincronización que olvidar. Cuando se olvida, el error es silencioso: nada se rompe, solo se muestra un valor viejo.

Qué pasa cuando una app crece sin framework

Sección titulada «Qué pasa cuando una app crece sin framework»

Complejidad que aparece

  • Estado disperso en múltiples variables globales
  • Código duplicado para actualizar vistas similares
  • Difícil reutilizar un “componente” de UI
  • Sin convención de estructura, cada equipo inventa la suya
  • Las pruebas deben simular el DOM completo
  • Gestionar dependencias entre módulos se vuelve manual

Lo que un framework aporta

  • Estado encapsulado por componente o servicio
  • Sincronización automática vista ↔ estado
  • Unidad de reutilización formal: el componente
  • Estructura de proyecto predefinida y compartida
  • Arquitectura que facilita las pruebas unitarias
  • Sistema de inyección de dependencias integrado

El cambio conceptual clave: de imperativo a declarativo

Sección titulada «El cambio conceptual clave: de imperativo a declarativo»

El salto mental más importante al adoptar Angular no es aprender su sintaxis, es cambiar la forma de pensar:

Esto es posible porque Angular compila los templates: convierte el HTML con sintaxis Angular en código JavaScript optimizado que sabe exactamente qué partes del DOM deben actualizarse cuando cada pieza del estado cambia.

La unidad fundamental de Angular, el building block, es el componente. Todo lo que el usuario ve en pantalla está construido con componentes. Entender qué es un componente, qué contiene y cómo se relacionan entre sí es la base de todo lo demás.

Un componente es una pieza de UI autocontenida que encapsula tres cosas:

Template (.html), Estructura

El HTML que describe qué se renderiza. Puede contener bindings ({{ }}), directivas (@if, @for) y eventos ((click)).

Clase (.ts), Comportamiento

TypeScript que define el estado del componente (propiedades) y su lógica (métodos). El template solo accede a lo que la clase expone.

Estilos (.css), Apariencia

CSS encapsulado: los estilos de un componente no afectan a otros componentes por defecto. Cada componente tiene su propio ámbito de estilos. Si no se usa este atributo, los estilos dele componente corresponden a los globales de la aplicación.

@Component({
selector: 'app-album-card', // etiqueta HTML personalizada
templateUrl: './album-card.component.html',
styleUrl: './album-card.component.css',
standalone: true, // Angular 17+: sin NgModule
})
export class AlbumCardComponent {
titulo: string = 'Thriller';
anio: number = 1982;
reproducir() {
console.log(`Reproduciendo ${this.titulo}`);
}
}

Declarativo en la práctica: binding y control flow

Sección titulada «Declarativo en la práctica: binding y control flow»

El template Angular extiende el HTML con sintaxis para reflejar el estado declarativamente:

Sintaxis Nombre Qué hace
{{ expresion }} Interpolación Muestra el valor de una expresión como texto
[propiedad]="valor" Property binding Pasa un valor a una propiedad del elemento o componente hijo
(evento)="handler()" Event binding Reacciona a eventos del DOM o del componente hijo
@if (condicion) { … } Control flow Renderiza el bloque solo si la condición es verdadera
@for (item of lista; track item) { … } Control flow Repite el bloque por cada elemento de la lista. track es obligatorio

Actividad interactiva: Practica binding y control flow

Sección titulada «Actividad interactiva: Practica binding y control flow»

Aquí puedes practicar la sintaxis de los templates. Hay dos paneles: uno muestra cómo se ve el código ejecutado, el otro muestra ejemplos de cada tipo de binding.

📖 Binding y Control Flow de Angular

Los cinco ejemplos son de un mismo componente. Cambia el estado en la columna izquierda y observa cómo el template lo refleja.

{{ }} Interpolación

Muestra el valor de una propiedad como texto

Clase (.ts)
nombre = 'Juan';
Template (.html)
<p>Hola {{ nombre }}</p>
Resultado

Hola Juan

[propiedad] Property Binding

Enlaza una propiedad del elemento a un valor de la clase

Clase (.ts)
bloqueado = true;
Template (.html)
<input [disabled]="bloqueado" />
Resultado

(evento) Event Binding

Ejecuta un método de la clase cuando ocurre un evento

Clase (.ts)
clicks = 0; contar() { this.clicks++;}
El botón vive en el template
Template (.html)
<button (click)="contar()"> Sumar</button> <p>Clicks: {{ clicks }}</p>
Resultado

Clicks: 0

@if Control Flow

Renderiza el bloque solo si la condición se cumple. No lo oculta: lo saca del DOM.

Clase (.ts)
mostrar = false;
Template (.html)
@if (mostrar) { <p>¡Aparecí!</p>}
Resultado
Qué queda en el DOM
@if (mostrar)

El nodo no existe mientras la condición sea falsa.

[hidden]="!mostrar"

El nodo existe siempre; solo deja de dibujarse.

En pantalla se ven igual. La diferencia está en el marcado, y por eso importa: lo que @if saca del DOM deja de ocupar memoria y de responder a eventos.

@for Control Flow

Repite el bloque por cada elemento de una lista

Clase (.ts)
items = [ 'Item 1', 'Item 2',];
Template (.html)
@for (item of items; track item) { <li>{{ item }}</li>}
Resultado
  • Item 1
  • Item 2
💡 track es obligatorio en @for: le dice a Angular cómo identificar cada elemento para no rehacer la lista entera cuando cambia.

Una aplicación Angular es un árbol de componentes. El componente raíz (AppComponent) contiene a todos los demás. Cada componente puede contener otros componentes, formando una jerarquía.

── AppComponent (raíz)
├── NavbarComponent
├── RouterOutlet ← el router inserta el componente activo aquí
│ ├── AlbumListComponent
│ │ └── AlbumCardComponent (se repite con @for)
│ └── AlbumDetailComponent
└── FooterComponent

Componer es poner el selector de un componente dentro del template de otro, como si fuera una etiqueta HTML propia. Ese acto es el que crea la relación padre → hijo y, al repetirse, forma el árbol.

<!-- album-list.component.html — el padre -->
<h2>Mis álbumes</h2>
<app-album-card /> <!-- ← esto es composición -->

El hijo no sabe nada de esto: AlbumCardComponent ignora quién lo usa y dónde aparece en la pantalla. Toda la responsabilidad de ubicarlo es del padre, que es quien lo llama.

import { Component } from '@angular/core';
import { AlbumCardComponent } from './album-card.component'; // 1. import de TypeScript
@Component({
selector: 'app-album-list',
imports: [AlbumCardComponent], // 2. permiso para usar <app-album-card> en el template
templateUrl: './album-list.component.html',
})
export class AlbumListComponent { }

Los componentes se pueden comunicar de distintas formas. Una de ellas es a través de un contrato formal que el hijo declara y el padre completa. La pregunta que decide cuál mecanismo usar no es “¿en qué dirección van los datos?”, sino ¿quién tiene permiso de escribir este valor?

Mecanismo Quién escribe el valor Cómo lo ata el padre Qué es en el hijo
input() Solo el padre. El hijo lo lee llamándolo: titulo(). [titulo]="album" Un InputSignal, de solo lectura: no tiene set()
output() El padre, cuando el hijo se lo pide emitiendo un evento (reproducir)="poner($event)" Un OutputEmitterRef (no es un signal); se usa con .emit(valor)
model() El hijo, y el padre se entera [(sonando)]="sonando" Un ModelSignal, que se puede escribir
Servicio compartido Cualquiera que lo inyecte No se ata en el template: inject(MiServicio) Un tercero que guarda el estado, útil entre componentes no relacionados

Con model() el padre no tiene que declarar nada más: Angular le crea al hijo un evento de salida con el nombre del input más Change (para sonando, un sonandoChange), y eso es lo que hace funcionar la sintaxis [( )].

Actividad interactiva: Comunicación entre componentes

Sección titulada «Actividad interactiva: Comunicación entre componentes»

Una tarjeta quiere ponerse a tocar una canción. Pruebe input() y output() —los dos mecanismos que compiten por resolver este problema— y observe qué pasa con la otra tarjeta: ahí está la diferencia entre escribir el valor ella misma y pedírselo al padre. model() y el servicio compartido, de la tabla de arriba, resuelven un problema distinto: uno en el que el valor sí le pertenece al hijo.

🔄 Comunicación entre componentes
La tarjeta quiere tocar una canción. ¿Cómo lo consigue?
Clase del padre · album-list.component.ts
export class AlbumListComponent {
  sonando = signal<string | null>(null);
  poner(t: string) { this.sonando.set(t); }
}

<!-- template -->
<app-album-card
  [titulo]="'Thriller'"
  [sonando]="sonando() === 'Thriller'"
  (reproducir)="poner($event)" />
<app-album-card [titulo]="'The Wall'" … />
Clase del hijo · album-card.component.ts
import { input, output } from '@angular/core';

export class AlbumCardComponent {
  titulo     = input.required<string>();
  sonando    = input(false);
  reproducir = output<string>();

  alHacerClic() {
    this.reproducir.emit(this.titulo());
  }
}
PADREAlbumListComponentaquí vive el estado
sonando()
null
[titulo] [sonando](reproducir)
HIJOAlbumCardComponent
titulo()
'Thriller'
sonando()
false
Thriller
HIJOAlbumCardComponent
titulo()
'The Wall'
sonando()
false
The Wall

Árbol de componentes
  • AppComponent
    • AlbumListComponentdueño de sonando
      • AlbumCardComponent[titulo]="'Thriller'"
      • AlbumCardComponent[titulo]="'The Wall'"

Es el mismo componente hijo dos veces, con un input() distinto cada uno. Los datos bajan por los corchetes [ ] y los eventos suben por los paréntesis ( ).

💡 Concepto: la pregunta no es "¿input ooutput?", sino ¿quién tiene permiso de escribir este valor? Con input(), solo el padre; por eso el intento de la tarjeta ni compila. Con output(), el padre puede escribirlo cuando el hijo se lo pide, y por eso sí logra coordinar a las dos tarjetas. El estado que coordina a varios hijos tiene que vivir en el padre.

Veremos más sobre cómo comunicar componentes más adelante.

Angular es un framework prescriptivo (opinionated, en inglés). Esto significa que el framework ya tomó decisiones de diseño por el equipo: cómo estructurar el proyecto, dónde vive la lógica, qué herramientas usar para routing, HTTP y formularios. No ofrece libertad total, pero reduce la fricción inicial y facilita el trabajo en equipo, porque todos siguen las mismas convenciones. Conocer sus piezas principales permite tomar decisiones de diseño informadas.

Pieza Responsabilidad Analogía
Componente Unidad de UI + comportamiento Un widget con estado propio
Servicio Lógica reutilizable, acceso a datos La capa de negocio del frontend
Router Navegar entre vistas según la URL El controlador de flujo de la app
HttpClient Comunicación con APIs REST El cliente HTTP del backend
Formularios reactivos Captura y validación de input del usuario Estado del formulario como objeto TypeScript
DI (Inyección de Dependencias) Proveer instancias de servicios a quien los necesite El contenedor de Spring, en el frontend

Hasta Angular 15 cada componente debía pertenecer a un modulo, una clase TS decorada con NgModule, la clase declaraba que componentes, directivas y servicios y declaraba sus dependencias a otros módulos. A partir de Angular 17 los componentes pueden ser standalone: se autodescriben, declaran sus propias importaciones y no necesitan NgModule.

Antes, con NgModule

@NgModule({
declarations: [AlbumListComponent],
imports: [CommonModule, HttpClientModule],
exports: [AlbumListComponent],
})
export class AlbumModule {}

Archivos adicionales obligatorios aunque el módulo hiciera poco.

Ahora, standalone

@Component({
standalone: true,
imports: [HttpClientModule],
template: ``,
})
export class AlbumListComponent {}

El componente declara sus dependencias directamente. Sin módulo intermediario.

Angular no ejecuta los templates en tiempo de ejecución. Los templates son compilados por el Angular Compiler durante el build a JavaScript optimizado. Esto tiene dos consecuencias importantes:

  • Los errores de template se detectan en tiempo de compilación, no en el browser.
  • El código resultante es más pequeño y rápido porque Angular sabe exactamente qué puede cambiar.

Señales (Signals), el nuevo modelo de reactividad

Sección titulada «Señales (Signals), el nuevo modelo de reactividad»

Angular 17 introdujo las signals como el mecanismo de reactividad de primera clase, complementando y gradualmente reemplazando a Zone.js.

  1. Angular 1–15 · Zone.js, Angular detecta cambios “envolviendo” las APIs del browser (setTimeout, eventos, HTTP). Cuando cualquiera de ellas se ejecuta, Angular revisa todo el árbol de componentes. Funciona, pero es poco preciso.

  2. Angular 16 · Signals (preview), Se introduce signal(), computed() y effect(). Un signal notifica exactamente qué cambió, Angular actualiza solo el componente que depende de ese valor.

  3. Angular 17+ · Signals estables, Signals son la forma recomendada para manejar estado local en componentes. Más predecible, mejor rendimiento, sin necesidad de Zone.js en el futuro.

  4. Angular 19 · La comunicación entre componentes también pasa a signals, input(), output() y model() se vuelven estables y reemplazan a los decoradores @Input() y @Output() en código nuevo.

Ya conoce la arquitectura del backend: tres capas (API REST, Lógica de Negocio, Persistencia) construidas en Java y Spring Boot. Angular es la capa cliente que vive en el browser del usuario y se comunica con ese backend a través de HTTP.

El stack completo

Angular y Spring Boot son independientes: hablan a través de HTTP con JSON. El contrato entre los dos lados, las URLs, los verbos, los DTOs, es lo que estudiaste en los artefactos del backend. Angular consume exactamente esos endpoints.

Concepto backend Equivalente Angular Qué conecta
Controller / @RestController Componente Angular Recibe la interacción del usuario, delega al servicio
Service / @Service Servicio Angular (@Injectable) Encapsula la lógica de llamadas HTTP y reglas del cliente
DTO (Data Transfer Object) Interface o clase TypeScript Define la forma del JSON que viaja entre capas
Repository HttpClient en el servicio Angular Accede a la fuente de datos, en este caso, la API REST
Validaciones (@NotNull, etc.) Validators en formularios reactivos Reglas de validación, en el cliente y en el servidor

Responsabilidades del frontend que no tiene el backend

Sección titulada «Responsabilidades del frontend que no tiene el backend»

Angular tiene responsabilidades que no existen en el servidor:

Estado de la UI

  • ¿Está el menú desplegado?
  • ¿Qué pestaña está activa?
  • ¿Hay una operación en curso (loading)?
  • ¿Qué mensaje de error mostrar?

Experiencia del usuario

  • Navegación sin recargar la página
  • Feedback inmediato en formularios
  • Actualización parcial de la vista
  • Caché de datos ya cargados

¿Ya tiene claro por qué Angular existe y cómo se arma un componente? Compruébelo con 6 preguntas antes de llegar al primer taller. No afecta la nota, es su radar personal. Si se equivoca, la explicación le dice por qué esa opción no es, sin revelarle cuál sí es, para que lo razone usted mismo.

1. Un compañero escribió un componente que actualiza el contador con this.contador++, pero la pantalla nunca cambia. ¿Cuál es la causa más probable?
2. ¿Qué convierte una clase TypeScript cualquiera en un componente de Angular?
3. ¿Cuál de las siguientes es una ventaja real que aporta un framework como Angular frente a escribir todo en JavaScript vanilla?
4. El material describe Angular como un framework prescriptivo (opinionated). ¿Qué ventaja trae esto para un equipo de desarrollo?
5. ¿Por qué Angular 17+ ya no exige NgModule para declarar un componente?
6. ¿Por qué un signal es más preciso que Zone.js para detectar cambios?