Saltar al contenido principal
Logo FPCode

Angular: fundamentos del framework

Contenido

Tutorial complementario de Angular para Desarrollo web en entorno cliente: entorno de trabajo, componentes standalone, TypeScript, plantillas, binding, control flow, señales, formularios, servicios e inyección de dependencias.

Introducción

Angular es un framework de desarrollo web para crear aplicaciones cliente con TypeScript. A diferencia de React, que se presenta como una biblioteca centrada en la interfaz, Angular ofrece una estructura más completa desde el principio: componentes, plantillas, inyección de dependencias, formularios, rutas, servicios, HTTP y herramientas oficiales de proyecto.

Este tutorial es complementario dentro de la asignatura Desarrollo web en entorno cliente. Su objetivo no es sustituir lo aprendido con JavaScript, DOM, asincronía o React, sino mostrar otro enfoque profesional para construir aplicaciones cliente. Angular resulta especialmente útil cuando se trabaja en proyectos grandes, con equipos que necesitan convenciones claras y una arquitectura homogénea.

Trabajaremos con el enfoque actual de Angular: componentes standalone, TypeScript, plantillas declarativas, control flow moderno con @if y @for, señales para estado local y servicios con inyección de dependencias.

Conocimiento previo

Referencias

Índice

  1. Qué aporta Angular frente a JavaScript puro y React
  2. Preparar el entorno de trabajo
  3. Crear una aplicación Angular
  4. Estructura inicial del proyecto
  5. Componentes standalone
  6. Plantillas: interpolación y expresiones
  7. Property binding y event binding
  8. Control flow moderno con @if y @for
  9. Señales para estado local
  10. Props de Angular: entradas y salidas
  11. Binding bidireccional con formularios simples
  12. Servicios e inyección de dependencias
  13. Organización básica de una aplicación Angular
  14. Proyecto guiado: gestor de tareas con Angular 14.1. Definir el modelo de tarea 14.2. Crear un servicio de tareas 14.3. Crear el componente de formulario 14.4. Crear la lista de tareas 14.5. Componer la aplicación
  15. Buenas prácticas iniciales
  16. Ejercicios

1. Qué aporta Angular frente a JavaScript puro y React

Con JavaScript puro puedes manipular el DOM, escuchar eventos y consumir APIs directamente. Ese enfoque es muy útil para comprender cómo funciona el navegador. Con React aprendiste a dividir la interfaz en componentes y a renderizar en función del estado. Angular comparte esa idea de componentes, pero añade una estructura de framework más completa.

Comparación inicial:

EnfoqueCaracterísticas principales
JavaScript puroControl directo del DOM, mínima abstracción, ideal para aprender fundamentos.
ReactBiblioteca de interfaz basada en componentes, JSX, estado y ecosistema flexible.
AngularFramework completo con TypeScript, plantillas, servicios, rutas, formularios, HTTP e inyección de dependencias.

Angular destaca cuando conviene que el proyecto tenga convenciones comunes desde el inicio. Por ejemplo, en una aplicación empresarial es habitual necesitar rutas, formularios complejos, comunicación HTTP, servicios compartidos, validación, interceptores y organización por funcionalidades. Angular proporciona piezas oficiales para todo ello.

Esto no significa que Angular sea siempre mejor. Para una interfaz pequeña puede ser excesivo. Para una aplicación amplia con muchas pantallas y equipo de desarrollo, su estructura puede ser una ventaja.

2. Preparar el entorno de trabajo

Para trabajar con Angular necesitas Node.js, npm y la CLI de Angular. Comprueba primero las versiones instaladas:

Terminal window
node --version
npm --version

La CLI de Angular permite crear proyectos, generar componentes y ejecutar el servidor de desarrollo.

Puedes crear una aplicación usando npx, sin instalar la CLI globalmente:

Terminal window
npx @angular/cli new gestor-tareas

También puedes instalar la CLI globalmente si el entorno del centro lo permite:

Terminal window
npm install -g @angular/cli
ng version

3. Crear una aplicación Angular

Para crear la aplicación:

Terminal window
npx @angular/cli new gestor-tareas

Durante la creación, la CLI puede preguntar por opciones como estilos, rutas o configuración. Para este primer proyecto puedes aceptar una configuración sencilla y usar CSS.

Entra en la carpeta y arranca el servidor:

Terminal window
cd gestor-tareas
npm start

O bien:

Terminal window
ng serve

La aplicación se abre normalmente en una dirección local como:

http://localhost:4200

Angular recompila la aplicación cuando modificas los archivos, de forma parecida a lo que viste con Vite en React.

4. Estructura inicial del proyecto

Una aplicación Angular moderna puede tener una estructura similar a esta:

src/
app/
app.config.ts
app.css
app.html
app.routes.ts
app.ts
index.html
main.ts

Archivos importantes:

ArchivoFunción
main.tsPunto de entrada de la aplicación. Arranca Angular en el navegador.
app.tsComponente principal.
app.htmlPlantilla del componente principal.
app.cssEstilos del componente principal.
app.config.tsConfiguración global de la aplicación.
app.routes.tsDefinición de rutas si el proyecto las usa.

Angular separa con claridad la lógica TypeScript, la plantilla HTML y los estilos del componente. También permite escribir plantillas y estilos dentro del decorador, pero en aplicaciones reales suele ser más cómodo separarlos.

5. Componentes standalone

Un componente Angular agrupa tres piezas:

  • Clase TypeScript con datos y métodos.
  • Plantilla HTML con la interfaz.
  • Estilos asociados al componente.

Ejemplo de componente standalone:

import { Component } from '@angular/core';
@Component({
selector: 'app-saludo',
templateUrl: './saludo.html',
styleUrl: './saludo.css',
})
export class Saludo {
nombre = 'Ada';
}

Plantilla saludo.html:

<section>
<h2>Hola, {{ nombre }}</h2>
<p>Este es un componente Angular.</p>
</section>

El selector indica cómo se usa el componente desde otra plantilla:

<app-saludo></app-saludo>

Si un componente usa otros componentes, directivas o módulos, los declara en imports:

import { Component } from '@angular/core';
import { Saludo } from './saludo/saludo';
@Component({
selector: 'app-root',
imports: [Saludo],
templateUrl: './app.html',
})
export class App {}

6. Plantillas: interpolación y expresiones

La interpolación permite mostrar valores de la clase en la plantilla:

export class ProductoCard {
nombre = 'Teclado mecánico';
precio = 49.95;
}
<article>
<h2>{{ nombre }}</h2>
<p>{{ precio }} €</p>
</article>

Dentro de {{ }} puedes usar expresiones sencillas:

<p>Precio con IVA: {{ precio * 1.21 }} €</p>
<p>{{ nombre.toUpperCase() }}</p>

Conviene evitar expresiones complejas en la plantilla. Si una operación requiere varias condiciones o transformaciones, es mejor prepararla en la clase TypeScript.

7. Property binding y event binding

El property binding conecta una propiedad del DOM o de un componente con un valor de la clase:

export class ImagenProducto {
imagenUrl = '/img/teclado.webp';
descripcion = 'Teclado mecánico retroiluminado';
}
<img [src]="imagenUrl" [alt]="descripcion" />

El event binding ejecuta un método cuando ocurre un evento:

export class Contador {
total = 0;
incrementar() {
this.total += 1;
}
}
<p>Total: {{ total }}</p>
<button type="button" (click)="incrementar()">Sumar</button>

Comparación con React:

ReactAngular
onClick={manejarClick}(click)="manejarClick()"
src={imagenUrl}[src]="imagenUrl"
{nombre}{{ nombre }}

8. Control flow moderno con @if y @for

Angular permite escribir condiciones y bucles en la plantilla. El control flow moderno usa @if, @else, @for y @switch.

Condicional:

@if (usuarioActivo) {
<p>Usuario conectado</p>
} @else {
<p>Usuario invitado</p>
}

Lista con @for:

export class ListaProductos {
productos = [
{ id: 1, nombre: 'Teclado' },
{ id: 2, nombre: 'Ratón' },
{ id: 3, nombre: 'Monitor' },
];
}
<ul>
@for (producto of productos; track producto.id) {
<li>{{ producto.nombre }}</li>
} @empty {
<li>No hay productos.</li>
}
</ul>

La parte track producto.id cumple una función parecida a la key de React: ayuda a Angular a identificar cada elemento de la lista de forma estable.

9. Señales para estado local

Las señales permiten guardar valores reactivos. Una señal se lee como una función y se actualiza con métodos como set o update.

import { Component, signal } from '@angular/core';
@Component({
selector: 'app-contador',
templateUrl: './contador.html',
})
export class Contador {
total = signal(0);
incrementar() {
this.total.update((valor) => valor + 1);
}
reiniciar() {
this.total.set(0);
}
}

Plantilla:

<p>Total: {{ total() }}</p>
<button type="button" (click)="incrementar()">Sumar</button>
<button type="button" (click)="reiniciar()">Reiniciar</button>

Observa que en la plantilla se lee total() y no total. Esa llamada obtiene el valor actual de la señal.

Las señales son útiles para estado local de componentes: contadores, filtros, campos, selección actual o visibilidad de un panel.

10. Props de Angular: entradas y salidas

En React usaste props para pasar datos de padre a hijo. En Angular se usan entradas y salidas. Una entrada permite que el componente reciba datos. Una salida permite que el componente avise de un evento al padre.

Ejemplo de componente hijo:

import { Component, input, output } from '@angular/core';
type Producto = {
id: number;
nombre: string;
precio: number;
};
@Component({
selector: 'app-producto-card',
templateUrl: './producto-card.html',
})
export class ProductoCard {
producto = input.required<Producto>();
eliminar = output<number>();
avisarEliminacion() {
this.eliminar.emit(this.producto().id);
}
}

Plantilla del hijo:

<article>
<h2>{{ producto().nombre }}</h2>
<p>{{ producto().precio }} €</p>
<button type="button" (click)="avisarEliminacion()">Eliminar</button>
</article>

Uso desde el padre:

<app-producto-card
[producto]="producto"
(eliminar)="eliminarProducto($event)"
></app-producto-card>

Relación conceptual:

ComunicaciónReactAngular
Padre a hijoPropsEntradas
Hijo a padreFunción recibida por propsSalidas con eventos

11. Binding bidireccional con formularios simples

Para formularios sencillos, Angular permite usar [(ngModel)]. Este patrón conecta el valor de un input con una propiedad del componente en ambos sentidos.

Primero importa FormsModule en el componente:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-formulario-nombre',
imports: [FormsModule],
templateUrl: './formulario-nombre.html',
})
export class FormularioNombre {
nombre = '';
}

Plantilla:

<label for="nombre">Nombre</label>
<input id="nombre" type="text" [(ngModel)]="nombre" />
<p>Hola, {{ nombre }}</p>

[(ngModel)] combina lectura y escritura. Es parecido a un formulario controlado en React, pero con sintaxis propia de Angular.

Para formularios grandes, validaciones complejas o lógica avanzada, Angular ofrece formularios reactivos. Se trabajarán en el tutorial complementario avanzado.

12. Servicios e inyección de dependencias

Un servicio es una clase que concentra lógica reutilizable que no pertenece a la presentación de un componente. Por ejemplo: gestionar tareas, acceder a una API, guardar preferencias o compartir datos.

Servicio básico:

import { Injectable } from '@angular/core';
export type Tarea = {
id: number;
titulo: string;
completada: boolean;
};
@Injectable({ providedIn: 'root' })
export class TareasService {
private tareas: Tarea[] = [
{ id: 1, titulo: 'Repasar componentes', completada: false },
{ id: 2, titulo: 'Practicar plantillas', completada: true },
];
obtenerTareas() {
return this.tareas;
}
}

Uso desde un componente con inject:

import { Component, inject } from '@angular/core';
import { TareasService } from './tareas.service';
@Component({
selector: 'app-lista-tareas',
templateUrl: './lista-tareas.html',
})
export class ListaTareas {
private tareasService = inject(TareasService);
tareas = this.tareasService.obtenerTareas();
}

La inyección de dependencias permite que Angular proporcione el servicio donde se necesita, sin crear manualmente instancias con new dentro de los componentes.

13. Organización básica de una aplicación Angular

Una estructura inicial razonable para una aplicación pequeña puede ser:

src/app/
components/
tarea-form/
tarea-list/
tarea-item/
services/
tareas.service.ts
models/
tarea.ts
app.ts
app.html
app.config.ts

Responsabilidades:

CarpetaContenido
componentsComponentes visuales y de interacción.
servicesLógica compartida, datos, comunicación con APIs.
modelsTipos e interfaces TypeScript.
app.*Componente raíz y configuración principal.

No conviene crear una estructura enorme para un proyecto pequeño. Empieza simple y separa cuando el código empiece a crecer.

14. Proyecto guiado: gestor de tareas con Angular

El proyecto de este tutorial será un gestor de tareas. Permitirá listar tareas, añadir nuevas tareas y marcar tareas como completadas. Usará componentes, señales, formularios simples, servicios e inyección de dependencias.

14.1. Definir el modelo de tarea

Archivo models/tarea.ts:

export type Tarea = {
id: number;
titulo: string;
completada: boolean;
};

Definir tipos ayuda a detectar errores antes de ejecutar la aplicación.

14.2. Crear un servicio de tareas

Archivo services/tareas.service.ts:

import { Injectable, signal } from '@angular/core';
import { Tarea } from '../models/tarea';
@Injectable({ providedIn: 'root' })
export class TareasService {
tareas = signal<Tarea[]>([
{ id: 1, titulo: 'Crear primer componente', completada: true },
{ id: 2, titulo: 'Practicar binding', completada: false },
]);
agregar(titulo: string) {
const nuevaTarea: Tarea = {
id: Date.now(),
titulo,
completada: false,
};
this.tareas.update((tareas) => [...tareas, nuevaTarea]);
}
alternar(id: number) {
this.tareas.update((tareas) =>
tareas.map((tarea) =>
tarea.id === id
? { ...tarea, completada: !tarea.completada }
: tarea,
),
);
}
}

El servicio mantiene la lista de tareas y ofrece métodos para modificarla.

14.3. Crear el componente de formulario

Archivo components/tarea-form/tarea-form.ts:

import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TareasService } from '../../services/tareas.service';
@Component({
selector: 'app-tarea-form',
imports: [FormsModule],
templateUrl: './tarea-form.html',
})
export class TareaForm {
private tareasService = inject(TareasService);
titulo = '';
agregarTarea() {
const tituloLimpio = this.titulo.trim();
if (tituloLimpio.length === 0) {
return;
}
this.tareasService.agregar(tituloLimpio);
this.titulo = '';
}
}

Plantilla tarea-form.html:

<form (ngSubmit)="agregarTarea()">
<label for="titulo">Nueva tarea</label>
<input id="titulo" name="titulo" [(ngModel)]="titulo" />
<button type="submit">Añadir</button>
</form>

El atributo name es necesario cuando se usa ngModel dentro de un formulario.

14.4. Crear la lista de tareas

Archivo components/tarea-list/tarea-list.ts:

import { Component, inject } from '@angular/core';
import { TareasService } from '../../services/tareas.service';
@Component({
selector: 'app-tarea-list',
templateUrl: './tarea-list.html',
})
export class TareaList {
tareasService = inject(TareasService);
}

Plantilla tarea-list.html:

<ul>
@for (tarea of tareasService.tareas(); track tarea.id) {
<li>
<label>
<input
type="checkbox"
[checked]="tarea.completada"
(change)="tareasService.alternar(tarea.id)"
/>
{{ tarea.titulo }}
</label>
</li>
} @empty {
<li>No hay tareas pendientes.</li>
}
</ul>

La plantilla lee la señal con tareasService.tareas().

14.5. Componer la aplicación

Archivo app.ts:

import { Component } from '@angular/core';
import { TareaForm } from './components/tarea-form/tarea-form';
import { TareaList } from './components/tarea-list/tarea-list';
@Component({
selector: 'app-root',
imports: [TareaForm, TareaList],
templateUrl: './app.html',
})
export class App {}

Plantilla app.html:

<main>
<h1>Gestor de tareas</h1>
<app-tarea-form></app-tarea-form>
<app-tarea-list></app-tarea-list>
</main>

El resultado muestra una aplicación pequeña, pero ya contiene las ideas esenciales de Angular: componentes, plantillas, formularios, servicios, señales e inyección de dependencias.

15. Buenas prácticas iniciales

  • Usa TypeScript para definir la forma de los datos.
  • Mantén componentes con responsabilidades claras.
  • Usa servicios para lógica compartida o acceso a datos.
  • No manipules el DOM manualmente si Angular puede renderizar la plantilla.
  • Usa @if y @for para condicionales y listas en contenido nuevo.
  • Usa track con identificadores estables en listas.
  • Asocia siempre los campos de formulario con label.
  • Evita mezclar demasiada lógica dentro de la plantilla.
  • No copies ejemplos antiguos de Angular sin revisar la documentación actual.

16. Ejercicios

Ejercicio 1: Crear una aplicación Angular

Crea una aplicación Angular nueva, arráncala en local y localiza los archivos main.ts, app.ts, app.html y app.config.ts.

Ejercicio 2: Primer componente standalone

Crea un componente Saludo que muestre un nombre y un mensaje. Úsalo desde el componente principal.

Ejercicio 3: Interpolación y property binding

Crea un componente ProductoCard que muestre nombre, precio e imagen usando interpolación y property binding.

Ejercicio 4: Event binding

Crea un contador con botones para sumar, restar y reiniciar usando (click).

Ejercicio 5: Control flow con @if y @for

Renderiza una lista de productos con @for y muestra un mensaje alternativo con @empty cuando la lista esté vacía.

Ejercicio 6: Señales

Reescribe el contador usando signal, set y update.

Ejercicio 7: Entrada y salida de componente

Crea un componente hijo que reciba un producto como entrada y emita un evento cuando el usuario pulse “Eliminar”.

Ejercicio 8: Formulario con ngModel

Crea un formulario simple con [(ngModel)] para escribir un nombre y mostrarlo en tiempo real.

Ejercicio 9: Servicio compartido

Crea un servicio que almacene una lista de tareas y consúmelo desde dos componentes distintos.

Ejercicio 10: Proyecto integrador

Construye el gestor de tareas de la unidad con servicio, señales, formulario, lista y marcado de tareas completadas.