Fundamentos de Angular: aprender desde cero con criterio

Por 2026-09-20
AngularTypeScriptFrontendSignalsDesarrollo WebFormación
Pantalla de portátil con código TypeScript sobre fondo oscuro — fundamentos de Angular desde cero
Foto de Nemuel Sereti en Pexels

Casi todo el mundo aprende Angular al revés: abre un tutorial, copia un componente y sigue adelante sin entender por qué el framework le pide las cosas de esa manera. Funciona hasta la primera pantalla complicada. Este artículo es el modelo mental que conviene tener antes de escribir el primer componente.

Llevo años trabajando con Angular en proyectos de empresa y, desde hace unos meses, escribiendo sobre él. Escribí Introducción a Angular precisamente por lo que veo repetido en la gente que empieza: no se atascan en la sintaxis, se atascan en las decisiones de diseño del framework. Nadie les ha explicado por qué existe la inyección de dependencias, ni qué problema resuelven los signals, ni por qué la lógica no va en el componente.

Cuando esas piezas encajan, Angular deja de parecer complicado y pasa a parecer previsible. Que es exactamente lo que buscan las empresas que lo eligen.

Voy a comparar con React más de una vez. Lo hago porque es el marco de referencia que casi todo el mundo trae puesto, y porque he escrito el libro de los dos: puedo decir dónde gana cada uno sin vender ninguno.

Angular es un marco completo, y eso cambia lo que tienes que aprender

La primera diferencia es de naturaleza, y explica casi todas las demás.

React es una librería de vistas. Te da componentes y estado, y el resto lo eliges tú: el router, el cliente HTTP, la librería de formularios, la gestión de estado global. Dos aplicaciones React de dos empresas distintas pueden parecerse muy poco.

Angular trae esas decisiones tomadas. El router es el de Angular. El cliente HTTP es HttpClient. Los formularios son los reactivos del propio framework. Hay un sistema de inyección de dependencias integrado y una CLI oficial que genera el esqueleto de todo.

Eso tiene una consecuencia práctica muy concreta: aprender Angular es aprender un conjunto cerrado de piezas, y ese conjunto es el mismo en todas las empresas. Un desarrollador que cambia de proyecto Angular reconoce la estructura el primer día. Es la razón por la que banca, seguros, administración pública y grandes corporaciones lo eligen: la rotación de personas duele menos.

El precio es la rampa de entrada. Las dos primeras semanas hay más cosas que sostener en la cabeza. A partir de la tercera, empieza a compensar.

TypeScript es la puerta, no un accesorio

Angular está escrito en TypeScript y su API lo da por hecho. Lo notas en la primera línea de cualquier archivo.

Esto es lo que hace falta de verdad, y es menos de lo que parece:

  • Anotaciones de tipo en variables, parámetros y retornos. precio: number, nombre: string, (id: number): Reserva.
  • Interfaces para describir la forma de los datos. La respuesta de tu API es una interfaz, y a partir de ahí el editor te autocompleta cada campo.
  • Clases con constructor, propiedades y modificadores private y readonly. Un componente Angular es una clase.
  • Decoradores: la sintaxis @Algo que va encima de una clase o de una propiedad. Un decorador es una función que recibe aquello que decora y le añade comportamiento o metadatos.
  • Genéricos, sobre todo para leer código ajeno. Cuando veas http.get<Reserva[]>('/api/reservas'), lo que dice esa línea es «esta petición devuelve un array de reservas», y el compilador lo va a hacer cumplir.
export interface Reserva {
  id: number;
  huesped: string;
  noches: number;
  precioNoche: number;
  confirmada?: boolean;
}

Ese ? en confirmada significa que el campo puede faltar. Es un detalle minúsculo que te ahorra toda una familia de fallos en ejecución, porque el compilador te obliga a contemplar el caso en que ese campo llega vacío.

Lo interesante para quien viene de JavaScript: los tipos desaparecen al compilar. En el navegador se ejecuta JavaScript normal. TypeScript es una red de seguridad que trabaja mientras escribes, no un lenguaje distinto que se ejecuta aparte.

Y una nota honesta: la parte de TypeScript que necesitas para Angular se aprende en unos días. La parte avanzada del lenguaje —tipos condicionales, infer, mapped types— es interesante y puede esperar.

El componente: una clase con una etiqueta HTML

Angular monta la interfaz como una jerarquía de componentes, y cada uno guarda junto su plantilla, sus estilos y su lógica.

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

@Component({
  selector: 'app-resumen-reserva',
  standalone: true,
  template: `
    <h3>{{ huesped }}</h3>
    <p>{{ noches }} noches</p>
  `
})
export class ResumenReservaComponent {
  huesped = 'Marta Ferrer';
  noches = 3;
}

Tres cosas que conviene entender de ese bloque.

El decorador es metadatos, no lógica. @Component({...}) no ejecuta nada por su cuenta: le dice al compilador de Angular cómo tratar esta clase. Qué etiqueta HTML la representa (selector), qué plantilla renderiza y qué estilos aplica. La clase de debajo es TypeScript corriente.

standalone: true significa que el componente se basta solo. Declara sus propias dependencias en un campo imports del decorador. Si has visto código Angular antiguo lleno de NgModule, olvídalo por ahora: desde Angular 17 la CLI genera componentes standalone sin que tengas que pedirlo, y los módulos quedaron como cosa de proyectos heredados. Aprender NgModules el primer día es de los peores usos que puedes dar a tu tiempo.

El selector convierte la clase en una etiqueta. Cuando Angular encuentra <app-resumen-reserva> en cualquier plantilla, monta ahí el componente. Ese app- del principio es una convención para distinguir tus componentes de los elementos HTML nativos. El prefijo se declara en angular.json y la CLI lo usa al generar; quien avisa si te lo saltas es el linter (@angular-eslint), no el framework.

Comparado con React: el equivalente de un componente React es una función que devuelve JSX. En Angular es una clase con un decorador y una plantilla aparte. La plantilla es HTML de verdad —con sintaxis añadida— en lugar de JavaScript que produce HTML. Más rígido, y más fácil de leer para alguien que llega de fuera.

La inyección de dependencias: lo que más cuesta y más devuelve

Si solo te llevas un concepto de este artículo, que sea este. Es el que separa a quien escribe Angular de quien escribe React con sintaxis de Angular.

El problema que resuelve se ve mejor al revés. Imagina que tu componente de reservas necesita datos y los pide él mismo: const servicio = new ReservasService(). Y que otro componente, el del contador de la barra superior, hace lo mismo. Ahora tienes dos objetos distintos en memoria, cada uno con su propio estado. El primero añade una reserva y el segundo ni se entera.

Angular resuelve esto invirtiendo quién manda. El componente no fabrica lo que necesita: lo pide y el framework se lo entrega, siempre la misma instancia.

import { Injectable, inject, signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ReservasService {
  private _reservas = signal<Reserva[]>([]);

  readonly reservas = this._reservas.asReadonly();
  readonly totalNoches = computed(() =>
    this._reservas().reduce((suma, r) => suma + r.noches, 0)
  );

  anadir(reserva: Reserva): void {
    this._reservas.update(lista => [...lista, reserva]);
  }
}

Y en el componente, una línea:

export class BarraSuperiorComponent {
  private reservasSvc = inject(ReservasService);
  total = this.reservasSvc.totalNoches;
}

providedIn: 'root' registra el servicio como instancia única de toda la aplicación. inject() la pide. Cualquier componente que la pida recibe exactamente el mismo objeto, así que el estado vive en un sitio y todos leen de ahí.

Tres consecuencias que no son obvias el primer día:

El componente adelgaza. Su trabajo queda reducido a mostrar datos y recoger acciones del usuario. La regla práctica: si es lógica de datos o de negocio, va al servicio; si es lógica de presentación, se queda en el componente. Calcular el total de una reserva es negocio. Decidir si el botón se pinta en gris es presentación.

Las pruebas cambian de dificultad. Un servicio se prueba creando la clase y llamando a sus métodos, sin renderizar nada. Si esa misma lógica vive dentro del componente, para probarla tienes que montar el componente entero.

El alcance es un árbol, no un interruptor. providedIn: 'root' cubre el 90% de los casos, pero también puedes declarar un servicio en el campo providers de un componente concreto: entonces ese componente y sus hijos reciben una instancia privada, que se destruye con ellos. Sirve para estado temporal que no debe salir de una pantalla —un formulario de varios pasos, la selección de una tabla—. Angular resuelve la petición buscando hacia arriba en el árbol y gana el primer proveedor que encuentra.

En React no hay equivalente directo. Lo más parecido es el contexto, que resuelve el mismo problema con bastante más ceremonia y sin las ventajas de la sustitución en pruebas.

Signals: reactividad sin listas de dependencias

Durante años, Angular detectaba cambios a lo bruto: una librería llamada Zone.js interceptaba todo lo asíncrono del navegador —clics, temporizadores, peticiones— y avisaba al framework de que algo podía haber cambiado. Angular entonces recorría el árbol entero comprobando expresiones. En una aplicación grande, eso son miles de comprobaciones por pulsación de tecla para descubrir que cambió un número en una esquina.

Los signals cambian el planteamiento: en lugar de buscar qué ha cambiado, el valor avisa de que ha cambiado y de quién depende de él.

import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-calculadora-estancia',
  standalone: true,
  template: `
    <p>{{ noches() }} noches · {{ total() }} €</p>
    <button (click)="anadirNoche()">Añadir noche</button>
  `
})
export class CalculadoraEstanciaComponent {
  noches = signal(2);
  precioNoche = signal(120);

  total = computed(() => this.noches() * this.precioNoche());

  anadirNoche() {
    this.noches.update(n => n + 1);
  }
}

Los paréntesis de noches() no son decoración: son la lectura. Y esa lectura es lo que registra la dependencia. Cuando Angular evalúa computed(() => this.noches() * this.precioNoche()), apunta que ese valor derivado depende de esos dos signals. Cuando uno cambia, recalcula. Cuando ninguno cambia, no hace nada.

Tres métodos y una regla:

  • .set(valor) cuando el nuevo valor no depende del anterior.
  • .update(fn) cuando sí depende. Recibe una función para que siempre trabaje sobre el valor más reciente.
  • computed() para cualquier valor derivado. Si puedes calcularlo a partir de otros, hazlo aquí en lugar de guardarlo aparte y sincronizarlo a mano.
  • La regla: con arrays y objetos, crea una referencia nueva en lugar de mutar la existente. lista.update(arr => [...arr, nuevo]) funciona; hacer push sobre el array y volver a asignarlo, no, porque la referencia es la misma y Angular compara referencias.

Queda effect(), para cuando algo tiene que salir del mundo reactivo: escribir en localStorage, registrar una métrica, hablar con una librería externa. Es la herramienta que más se usa mal. Si te descubres escribiendo un efecto para copiar el valor de un signal a otro signal, lo que querías era un computed().

La comparación que aclara esto si vienes de React

ReactAngularDiferencia real
useStatesignal()El signal vive en la clase, no en el orden de llamada del render
useMemocomputed()El computed descubre sus dependencias solo; useMemo te obliga a listarlas
useEffecteffect()Sin array de dependencias, y con limpieza ligada al ciclo de vida del componente

Ese es el punto: en Angular no hay array de dependencias. La clase de errores que en React nace de una lista incompleta —el efecto que no se vuelve a ejecutar, el valor memorizado que se queda antiguo— aquí no existe, porque la dependencia se registra en el momento de leer el valor.

A cambio, Angular te pide los paréntesis en cada lectura. Es la queja número uno de quien llega de React, y se pasa en dos días.

El ciclo de vida: menos de lo que te imaginas

Un componente nace, se actualiza y muere, y Angular ofrece ganchos para cada momento. La lista completa asusta. La lista útil es corta.

ngOnInit se ejecuta una vez, después de que Angular haya asignado los datos de entrada del componente. Es el sitio para cargar datos. La pregunta obligada es por qué no hacerlo en el constructor: porque cuando el constructor corre, los @Input todavía están vacíos. El constructor es para pedir dependencias; ngOnInit, para arrancar el trabajo.

ngOnDestroy se ejecuta al desmontar. Es donde se limpia lo que no se limpia solo: temporizadores, escuchas del DOM, suscripciones abiertas. Saltárselo es la fuga de memoria clásica de las aplicaciones Angular.

ngOnChanges reacciona cuando cambia un dato de entrada. Y aquí viene lo bueno: con signals se necesita mucho menos.

export class DetalleReservaComponent {
  reservaId = input.required<number>();

  private reservasSvc = inject(ReservasService);

  reserva = computed(() =>
    this.reservasSvc.buscarPorId(this.reservaId())
  );
}

input.required<number>() declara una entrada obligatoria que es un signal. El computed de debajo se recalcula solo cada vez que el padre pasa otro identificador. Sin ngOnChanges, sin SimpleChanges, sin comparar el valor anterior con el nuevo.

Para las suscripciones, el equivalente moderno de ngOnDestroy es takeUntilDestroyed(), que ata la vida de la suscripción a la del componente y te ahorra escribir la limpieza.

La conclusión práctica: en un proyecto nuevo con Angular 17 o superior, vas a usar ngOnInit a menudo, ngOnDestroy de vez en cuando y el resto casi nunca. Aprende esos dos bien y deja los demás para cuando aparezcan en código heredado.

Las plantillas: corchetes, paréntesis y poco más

La sintaxis de las plantillas de Angular es lo que más extraña al principio y lo que antes se automatiza, porque son cuatro formas y una lógica.

<img [src]="reserva.foto">              <!-- entra un dato -->
<button (click)="confirmar()">          <!-- sale un evento -->
<input [(ngModel)]="filtro">            <!-- entra y sale -->
<p>{{ reserva.huesped }}</p>            <!-- se interpola texto -->

Corchetes: el dato entra en el elemento. Paréntesis: el evento sale hacia tu clase. Los dos juntos, [()], son las dos cosas a la vez, y en la comunidad se conocen como banana in a box por el dibujo que hacen. Con esa regla se lee cualquier plantilla de Angular.

Un aviso sobre el tercer ejemplo: [(ngModel)] no viene activado de serie. Pertenece a FormsModule, así que en un componente standalone hay que añadirlo al imports del decorador o la plantilla no compila. Es el primer tropiezo de casi todo el mundo, y no es culpa tuya: el error que da no menciona FormsModule por ninguna parte.

El control de flujo moderno es HTML con bloques, y llegó en Angular 17 para sustituir a las viejas directivas *ngIf y *ngFor:

@if (reservas().length > 0) {
  @for (r of reservas(); track r.id) {
    <app-resumen-reserva [reserva]="r" />
  }
} @else {
  <p>Todavía no hay reservas.</p>
}

Ese track r.id es obligatorio y tiene motivo: le dice a Angular cómo identificar cada elemento entre dos renderizados, para que reutilice los nodos del DOM en lugar de rehacer la lista. Es el equivalente exacto de la key de React, con la diferencia de que aquí el compilador te obliga a ponerlo.

Y los pipes, la transformación de presentación:

<p>{{ reserva.precioNoche | currency:'EUR' }}</p>
<p>{{ reserva.entrada | date:'dd/MM/yyyy' }}</p>

Formatear moneda y fechas con el locale correcto viene resuelto de serie. Es una de esas cosas pequeñas que en un proyecto sin framework acaban siendo una utilidad casera mal probada.

El orden en que conviene aprender todo esto

Aquí es donde la mayoría de tutoriales fallan: enseñan las piezas, sin decir en qué orden encajarlas. Este es el recorrido que sigo cuando formo a alguien, y el que estructura el libro.

  1. TypeScript, una semana escasa. Tipos, interfaces, clases, decoradores y genéricos. Se practica sin Angular, ejecutando archivos sueltos. Saltarse esto es la causa número uno de que después «Angular parezca magia».
  2. La CLI y la estructura del proyecto. ng new, ng serve, ng generate. Entender qué genera cada comando y qué hace cada archivo del esqueleto.
  3. Componentes y comunicación padre-hijo. Entradas, salidas, interpolación, @if y @for. Aquí ya construyes pantallas de verdad.
  4. Servicios e inyección de dependencias. El salto conceptual grande. Sacar la lógica del componente y ponerla donde vive el estado.
  5. Signals. Convertir ese estado del servicio en reactivo y ver cómo la interfaz se actualiza sola.
  6. Router, formularios reactivos y HttpClient. Las tres piezas que convierten un ejercicio en una aplicación.

Y lo que conviene aplazar a propósito, aunque lo encuentres por todas partes:

  • NgModules. Patrón heredado. Lo necesitarás el día que entres en un proyecto antiguo, y ese día lo aprendes en una tarde.
  • RxJS a fondo. Con map, switchMap, catchError y debounceTime cubres casi todo lo que hace falta al principio. El resto de la librería es enorme y distrae.
  • NgRx y compañía. Un servicio con signals resuelve la gestión de estado de la inmensa mayoría de aplicaciones. Añadir una librería de estado global el primer mes es resolver un problema que todavía no tienes.
  • Renderizado en servidor. Interesante, y muy posterior.

Cinco errores que veo repetidos

Meter la lógica de negocio en el componente. Empieza siendo cómodo y acaba con el mismo cálculo duplicado en tres pantallas, cada una con su matiz. La pregunta que lo evita: ¿esto lo necesitaría otro componente? Si la respuesta es sí, va al servicio.

Suscribirse sin limpiar. Cada .subscribe() sin takeUntilDestroyed() ni ngOnDestroy es una fuga esperando a producirse. En páginas que se montan y desmontan mucho, se nota.

Mutar arrays dentro de un signal. lista().push(x) compila, se ejecuta y no actualiza la pantalla. La referencia no cambió, así que Angular considera que no pasó nada. Hay que crear un array nuevo.

Usar effect() como si fuera computed(). Un efecto que escribe en otro signal crea una dependencia circular esperando su turno y hace el flujo de datos imposible de seguir. Si el valor se puede derivar, se deriva.

Copiar patrones de tutoriales de 2019. Angular cambió mucho entre la versión 8 y la 17. NgModule, *ngIf, EventEmitter por todas partes y constructores de quince parámetros son señales de que ese material está caducado. Mira siempre la fecha y la versión.

¿Angular o React?

Me lo preguntan constantemente, y como he escrito los dos libros la respuesta no me conviene en ninguna dirección.

React se empieza a usar antes. Una función que devuelve HTML, useState y ya estás produciendo. La contrapartida aparece en el proyecto tres: cada equipo ha montado su propia combinación de router, estado, formularios y peticiones, y esas decisiones hay que reaprenderlas cada vez.

Angular cuesta más las primeras semanas y se estabiliza antes. El precio es TypeScript, decoradores e inyección de dependencias desde el minuto uno. Lo que compras es un conjunto de piezas oficial que se parece en todas las empresas y sobrevive a la rotación de gente.

Dicho en términos de trabajo: en España, Angular pesa mucho en banca, seguros, telecomunicaciones, administración pública y software de empresa; React domina producto, startups y agencias. Las dos listas de ofertas son largas.

Si quieres la otra mitad de esta comparación desde el lado contrario, lo cuento en los fundamentos de React: estado y efectos, donde el modelo mental es justo el inverso: menos estructura impuesta y más decisiones tuyas.

Por dónde seguir

Si has llegado hasta aquí tienes el mapa: qué piezas tiene Angular, para qué está cada una y en qué orden encajarlas. Lo que queda es escribir código, y ahí no hay atajo.

Mi recomendación concreta: monta un proyecto con ng new, elige una aplicación pequeña que te interese de verdad y recorre los seis pasos de arriba en orden, sin saltarte el cuarto. La inyección de dependencias es donde Angular deja de parecerse a lo demás.

Si prefieres el camino guiado, eso es exactamente lo que hace Introducción a Angular: TypeScript desde los fundamentos, después componentes, servicios, signals, router, formularios reactivos y HttpClient, y al final una aplicación completa construida paso a paso. Está escrito para Angular 17 o superior, con componentes standalone y signals, sin patrones heredados que después haya que desaprender. Si vienes o vas hacia el otro lado, Introducción a React es el volumen anterior de la misma serie.

Y si lo que tienes entre manos es un proyecto Angular de empresa que ya duele —una aplicación heredada, una migración pendiente, un equipo que no avanza— eso es otra conversación, y también me dedico a ella: cuéntame el caso.

¿Buscas un consultor IT para tu empresa? Conoce mis servicios de consultoría IT: arquitectura cloud, desarrollo fullstack y liderazgo técnico. ¿Empresa en Mallorca o Baleares? Consultor IT en Mallorca.

¿Listo para transformar tu stack tecnológico?

Hablemos sobre cómo llevar tus sistemas al siguiente nivel, optimizar el rendimiento y potenciar el talento de tu equipo.