Cuando hablamos de conceptos clave de Angular, nos referimos a las bases que permiten desarrollar aplicaciones web modernas, escalables y eficientes con este potente framework de JavaScript. Desde su sistema de reactividad con señales, pasando por la comunicación entre componentes y el manejo de rutas, hasta llegar a despliegues optimizados, todo forma parte de un ecosistema que ofrece soluciones integrales.
En las siguientes secciones vas a conocer, además de la teoría, ejemplos prácticos que puedes aplicar de inmediato en tus proyectos. Y si quieres partir de un punto más inicial, te recomiendo revisar ¿Cómo empezar con Angular desde cero? Guía paso a paso para principiantes, donde verás cómo preparar el entorno y crear tu primer componente.
Conceptos clave de Angular para la reactividad con señales (signal
, computed
y effect
)
Uno de los pilares de Angular en su versión más reciente es el sistema de señales. Este enfoque permite que la reactividad sea más predecible, sencilla y eficiente que con métodos anteriores como RxJS
para estados simples.
signal
: almacena un valor que puede cambiar con el tiempo y notifica a quien lo consume.computed
: calcula un valor derivado en función de otros valores reactivos, sin necesidad de actualizaciones manuales.effect
: ejecuta acciones secundarias (side-effects) cuando cambian las señales que observa, por ejemplo guardar datos enlocalStorage
.
Ejemplo práctico:
import { signal, computed, effect } from '@angular/core';
export class Counter {
count = signal(0);
double = computed(() => this.count() * 2);
constructor() {
effect(() => {
console.log(`El contador vale: ${this.count()}`);
localStorage.setItem('counter', this.count().toString());
});
}
increment() {
this.count.update(c => c + 1);
}
}
En este código, double
se recalcula automáticamente cada vez que cambia count
. Además, gracias al effect
, se guarda su valor en el navegador sin que tengamos que añadir lógica extra en cada evento.
Conceptos clave de Angular en servicios e inyección de dependencias
En los conceptos clave de Angular no puede faltar su sistema de inyección de dependencias (DI), que nos permite gestionar y compartir lógica de negocio en toda la aplicación sin duplicación de código.
Ventajas principales:
- Permite un estado centralizado y coherente.
- Facilita pruebas unitarias al aislar la lógica.
- Reduce el acoplamiento entre componentes.
Ejemplo:
import { Injectable } from '@angular/core';
import { signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UserService {
user = signal<{ name: string; loggedIn: boolean }>({ name: '', loggedIn: false });
login(name: string) {
this.user.set({ name, loggedIn: true });
}
logout() {
this.user.set({ name: '', loggedIn: false });
}
}
Este servicio puede ser inyectado en cualquier componente, manteniendo un estado único y accesible.
Comunicación entre componentes con input()
y output()
Otro de los pilares de Angular es la comunicación fluida entre componentes. Actualmente, con la API de señales, esto es más intuitivo que nunca.
input()
: recibe datos desde un componente padre.output()
: emite eventos hacia el padre.
Ejemplo:
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-todo-item',
standalone: true,
template: `
<li>
{{ todo().title }}
<button (click)="remove.emit(todo().id)">Eliminar</button>
</li>
`
})
export class TodoItem {
todo = input<{ id: string; title: string }>();
remove = output<string>();
}
Este patrón asegura que los datos fluyen de forma unidireccional, lo que mejora la claridad y el mantenimiento del código.
Uso de linkedSignal()
dentro de los conceptos clave de Angular para estados dependientes
Entre los conceptos clave de Angular más recientes, encontramos linkedSignal()
, que resuelve un problema habitual: tener un valor dependiente de otro, pero que también pueda modificarse manualmente sin perder su referencia principal.
Ejemplo:
import { linkedSignal, signal } from '@angular/core';
const filters = signal(['all', 'active', 'done']);
const selectedFilter = linkedSignal({
source: filters,
computation: (filtros, prev) => prev?.value ?? filtros[0]
});
Esto permite que si los filtros disponibles cambian, la selección actual se ajuste automáticamente, aunque el usuario pueda elegir manualmente otra opción.
Configuración de rutas y navegación en Angular
El router es fundamental dentro de los conceptos clave de Angular, ya que define la experiencia de navegación de la aplicación. Nos permite gestionar rutas, cargar componentes de forma perezosa y decidir la estrategia de ubicación, ya sea con URLs limpias o hash (withHashLocation()
).
Ejemplo:
import { provideRouter, withHashLocation } from '@angular/router';
export const routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', loadComponent: () => import('./home.component').then(m => m.HomeComponent) }
];
provideRouter(routes, withHashLocation());
Si quieres profundizar en detalles como RouterLink
, control flow
o ngClass
, te recomiendo esta guía de Angular sobre RouterLink, control flow y comunicación.
Ejemplo práctico: miniaplicación de tareas con Angular
Para unir todos estos conceptos clave de Angular, vamos a construir un gestor de tareas que use señales, comunicación entre componentes, un servicio y persistencia en localStorage
.
Estructura del store de tareas
import { Injectable, signal, computed, effect } from '@angular/core';
export interface Task { id: string; title: string; done: boolean; }
@Injectable({ providedIn: 'root' })
export class TaskStore {
tasks = signal<Task[]>([]);
remaining = computed(() => this.tasks().filter(t => !t.done).length);
constructor() {
effect(() => {
localStorage.setItem('tasks', JSON.stringify(this.tasks()));
});
}
add(title: string) {
this.tasks.update(ts => [...ts, { id: crypto.randomUUID(), title, done: false }]);
}
}
Componentes padre e hijo con señales
Hijo:
import { Component, input, output } from '@angular/core';
import { Task } from './task.store';
@Component({
selector: 'app-task-item',
standalone: true,
template: `
<div>
<input type="checkbox" [checked]="task().done" (change)="toggle.emit(task().id)" />
{{ task().title }}
</div>
`
})
export class TaskItem {
task = input<Task>();
toggle = output<string>();
}
Padre:
@Component({
selector: 'app-task-page',
standalone: true,
template: `
<h1>Mis tareas ({{ store.remaining() }} pendientes)</h1>
<form (ngSubmit)="addTask()">
<input [(ngModel)]="title" name="title" placeholder="Nueva tarea" />
<button>Añadir</button>
</form>
<app-task-item *ngFor="let t of store.tasks()" [task]="t" (toggle)="toggleDone($event)"></app-task-item>
`
})
export class TaskPage {
title = '';
constructor(public store: TaskStore) {}
addTask() { if (this.title.trim()) { this.store.add(this.title); this.title = ''; } }
toggleDone(id: string) { /* lógica */ }
}
Buenas prácticas para desplegar aplicaciones Angular
Cuando despliegas tu aplicación, recuerda que si tu hosting es estático (como GitHub Pages o Netlify), lo ideal es usar hash routing con withHashLocation()
. Esto evita errores al recargar páginas que no están mapeadas en el servidor.
En cambio, si tu aplicación se aloja en un servidor configurado con rewrites (por ejemplo Nginx, Apache o Vercel), puedes usar rutas limpias para una mejor apariencia y SEO.
Conclusión y siguientes pasos
Ahora ya conoces los conceptos clave de Angular y cómo aplicarlos en ejemplos prácticos. Has visto señales, servicios, comunicación entre componentes, linkedSignal
y configuración de rutas, todo en un contexto realista.
El siguiente paso puede ser profundizar en áreas más avanzadas como lazy loading, guards, interceptores HTTP o optimización de rendimiento.
Para complementar lo aprendido, puedes visitar la documentación oficial de Angular o artículos especializados como la referencia de señales en Angular.
Una respuesta