Angular puede intimidar al principio. Es un framework completo con muchas piezas: módulos, componentes, servicios, decoradores, TypeScript… Parece mucho. Pero si lo aprendes paso a paso, todo encaja de forma natural.
En esta guía vamos a construir una aplicación Angular completa desde cero: una lista de tareas con componentes, servicios, routing y peticiones HTTP. Al final tendrás las bases para construir cualquier aplicación Angular real.
En este artículo:
- Instalación y configuración del entorno
- Estructura de un proyecto Angular
- Tu primer componente
- Data binding: conectar datos y plantillas
- Servicios e inyección de dependencias
- Routing: navegación entre vistas
- Peticiones HTTP con HttpClient
- Signals: el futuro del estado en Angular
Instalación y configuración del entorno
Para trabajar con Angular necesitas Node.js (versión 18 o superior) y el CLI de Angular. Si no tienes Node.js, instálalo desde nodejs.org.
Una vez tienes Node.js, instala el CLI de Angular globalmente:
# Instalar Angular CLI globalmente
npm install -g @angular/cli
# Verificar la versión instalada
ng version
Ahora crea tu primer proyecto:
# Crear un nuevo proyecto Angular
ng new mi-primera-app
# El CLI te pregunta:
# ¿Añadir routing? → Yes
# ¿Qué formato de estilos? → CSS (o SCSS si lo prefieres)
# Entrar en el directorio del proyecto
cd mi-primera-app
# Arrancar el servidor de desarrollo
ng serve
Abre http://localhost:4200 en tu navegador. Verás la página de bienvenida de Angular. El servidor de desarrollo detecta cambios automáticamente y recarga la página.
Estructura de un proyecto Angular
Un proyecto Angular recién creado tiene esta estructura principal:
mi-primera-app/
├── src/
│ ├── app/
│ │ ├── app.component.ts # Componente raíz
│ │ ├── app.component.html # Plantilla del componente raíz
│ │ ├── app.component.css # Estilos del componente raíz
│ │ ├── app.config.ts # Configuración de la app
│ │ └── app.routes.ts # Definición de rutas
│ ├── main.ts # Punto de entrada de la aplicación
│ └── index.html # HTML principal
├── angular.json # Configuración del proyecto
├── package.json # Dependencias
└── tsconfig.json # Configuración de TypeScript
En Angular moderno (a partir de la versión 17), los proyectos nuevos usan componentes standalone por defecto, lo que simplifica mucho la estructura eliminando la necesidad de NgModules.
Tu primer componente
Un componente en Angular es una clase TypeScript con un decorador @Component que define la plantilla HTML, los estilos y la lógica del componente.
Genera un nuevo componente con el CLI:
ng generate component tareas
# o la versión corta:
ng g c tareas
Esto crea los archivos del componente. Así es cómo queda el componente de tareas:
// tareas.component.ts
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
interface Tarea {
id: number;
texto: string;
completada: boolean;
}
@Component({
selector: 'app-tareas',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './tareas.component.html',
styleUrl: './tareas.component.css'
})
export class TareasComponent {
tareas = signal<Tarea[]>([
{ id: 1, texto: 'Aprender Angular', completada: false },
{ id: 2, texto: 'Crear un componente', completada: true },
]);
nuevaTarea = '';
nextId = 3;
agregarTarea() {
if (this.nuevaTarea.trim()) {
this.tareas.update(tareas => [
...tareas,
{ id: this.nextId++, texto: this.nuevaTarea.trim(), completada: false }
]);
this.nuevaTarea = '';
}
}
toggleTarea(id: number) {
this.tareas.update(tareas =>
tareas.map(t => t.id === id ? { ...t, completada: !t.completada } : t)
);
}
eliminarTarea(id: number) {
this.tareas.update(tareas => tareas.filter(t => t.id !== id));
}
}
Data binding: conectar datos y plantillas
El data binding es la forma en que Angular conecta los datos del componente con el HTML. Hay cuatro tipos:
Interpolación ({{ }}): muestra el valor de una variable en el HTML.
<!-- Interpolación -->
<h1>{{ titulo }}</h1>
<p>Total de tareas: {{ tareas().length }}</p>
Property binding ([propiedad]="valor"): enlaza una propiedad HTML con una expresión del componente.
<!-- Property binding -->
<input [value]="nuevaTarea" [disabled]="cargando">
<img [src]="imagen.url" [alt]="imagen.descripcion">
Event binding ((evento)="handler()"): escucha eventos del DOM y ejecuta métodos del componente.
<!-- Event binding -->
<button (click)="agregarTarea()">Añadir</button>
<input (keyup.enter)="agregarTarea()" (input)="onInput($event)">
Two-way binding ([(ngModel)]): sincronización bidireccional entre el input y la variable (requiere FormsModule).
<!-- Two-way binding -->
<input [(ngModel)]="nuevaTarea" placeholder="Nueva tarea...">
Servicios e inyección de dependencias
Los servicios en Angular son clases que encapsulan lógica de negocio, acceso a datos o funcionalidad compartida entre componentes. Angular los inyecta automáticamente donde los necesitas.
ng generate service tareas
// tareas.service.ts
import { Injectable, signal } from '@angular/core';
export interface Tarea {
id: number;
texto: string;
completada: boolean;
}
@Injectable({
providedIn: 'root' // disponible en toda la app sin configuración extra
})
export class TareasService {
private _tareas = signal<Tarea[]>([]);
readonly tareas = this._tareas.asReadonly();
agregar(texto: string) {
const nueva: Tarea = {
id: Date.now(),
texto,
completada: false
};
this._tareas.update(t => [...t, nueva]);
}
toggle(id: number) {
this._tareas.update(t =>
t.map(tarea => tarea.id === id ? { ...tarea, completada: !tarea.completada } : tarea)
);
}
eliminar(id: number) {
this._tareas.update(t => t.filter(tarea => tarea.id !== id));
}
}
Para usar el servicio en un componente, simplemente inyéctalo en el constructor:
// En el componente
constructor(public tareasService: TareasService) {}
Routing: navegación entre vistas
Angular Router te permite crear aplicaciones de página única (SPA) con navegación entre diferentes vistas sin recargar la página. La configuración de rutas se define en app.routes.ts:
// app.routes.ts
import { Routes } from '@angular/router';
import { TareasComponent } from './tareas/tareas.component';
import { DetalleComponent } from './detalle/detalle.component';
import { NotFoundComponent } from './not-found/not-found.component';
export const routes: Routes = [
{ path: '', redirectTo: '/tareas', pathMatch: 'full' },
{ path: 'tareas', component: TareasComponent },
{ path: 'tareas/:id', component: DetalleComponent },
{ path: '**', component: NotFoundComponent } // ruta 404
];
En tu plantilla, usa RouterLink para navegar y RouterOutlet para renderizar la vista activa:
<!-- Navegación -->
<nav>
<a routerLink="/tareas" routerLinkActive="activo">Mis Tareas</a>
</nav>
<!-- Aquí se renderiza el componente de la ruta activa -->
<router-outlet></router-outlet>
Peticiones HTTP con HttpClient
Para consumir APIs REST en Angular, usas HttpClient. Primero configúralo en app.config.ts:
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient()
]
};
Luego úsalo en un servicio:
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Post {
id: number;
title: string;
body: string;
}
@Injectable({ providedIn: 'root' })
export class ApiService {
private apiUrl = 'https://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) {}
getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(`${this.apiUrl}/posts`);
}
getPost(id: number): Observable<Post> {
return this.http.get<Post>(`${this.apiUrl}/posts/${id}`);
}
}
Signals: el futuro del estado en Angular
A partir de Angular 16, los Signals son la nueva forma recomendada de gestionar el estado reactivo. Son más simples que los Observables para la mayoría de casos de uso y permiten un rendimiento mejor gracias a la detección de cambios más granular.
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-contador',
standalone: true,
template: `
<p>Contador: {{ contador() }}</p>
<p>El doble: {{ doble() }}</p>
<button (click)="incrementar()">+1</button>
<button (click)="decrementar()">-1</button>
`
})
export class ContadorComponent {
// Signal: valor reactivo
contador = signal(0);
// Computed: valor derivado que se actualiza automáticamente
doble = computed(() => this.contador() * 2);
constructor() {
// Effect: reacciona a cambios en signals
effect(() => {
console.log('El contador cambió a:', this.contador());
});
}
incrementar() {
this.contador.update(v => v + 1);
}
decrementar() {
this.contador.update(v => v - 1);
}
}
Con esto ya tienes una base sólida para construir aplicaciones Angular reales. Los siguientes pasos naturales son profundizar en RxJS para peticiones HTTP avanzadas, aprender Angular Forms reactivos para formularios complejos, y explorar técnicas de optimización de rendimiento como lazy loading y OnPush change detection.