Hoja de atajos de Angular¶
Referencia rápida para consultar mientras escribes código. La teoría está en la guía de estudio.
Cubre los mismos temas que la hoja de atajos de Angular de Fernando Herrera, reescritos y puestos al día.
Cómo leer esta página¶
Angular cambió mucho en pocos años, así que hay dos formas de escribir casi todo. Aquí cada tema muestra primero la forma actual y después, en un bloque marcado como Legacy, la forma antigua.
- Actual: lo que se usa en código nuevo.
- Legacy: la forma antigua. Sigue funcionando y la vas a ver en proyectos existentes y en tutoriales viejos, pero no se usa en código nuevo.
- Legacy (obsoleto): además, Angular lo marcó oficialmente como obsoleto (deprecated) y piensa eliminarlo.
Si un tema no tiene bloque Legacy, es porque sólo existe una forma.
Legacy y actual, de un vistazo¶
| Tema | Legacy | Actual | ¿Obsoleto? |
|---|---|---|---|
| Organización | NgModule que declara los componentes |
Componentes standalone | No |
| Condiciones y listas | *ngIf, *ngFor, [ngSwitch] |
@if, @for, @switch |
Sí |
| Clases y estilos | [ngClass], [ngStyle] |
[class.x], [style.x] |
No |
| Datos que entran | @Input() |
input() |
No |
| Eventos que salen | @Output() con EventEmitter |
output() |
No |
| Enlace en los dos sentidos | @Input() + @Output() con sufijo Change |
model() |
No |
| Elementos del template | @ViewChild(), @ViewChildren() |
viewChild(), viewChildren() |
No |
| Etiqueta del componente | @HostBinding(), @HostListener() |
host: { ... } en @Component |
No |
| Pedir dependencias | Parámetros del constructor |
inject() |
No |
| Estado que cambia | Propiedades comunes y observables | Signals | No |
| Registrar las rutas | RouterModule.forRoot(routes) |
provideRouter(routes) |
No |
| Lazy loading | loadChildren hacia un módulo |
loadComponent, o loadChildren hacia rutas |
No |
| Guards | Clases que implementan CanActivate |
Funciones (CanActivateFn) |
Sí |
| Guard de carga | CanLoad |
CanMatchFn |
Sí |
| Interceptores | Clases que implementan HttpInterceptor |
Funciones (HttpInterceptorFn) |
No |
| Cliente HTTP | HttpClientModule |
provideHttpClient() |
Sí |
| Nombres de archivo | hero-card.component.ts, clase HeroCardComponent |
hero-card.ts, clase HeroCard |
No |
Angular CLI¶
npm install -g @angular/cli # instala el CLI
ng new my-app # crea un proyecto
ng serve # servidor de desarrollo
ng build # compila para producción
ng generate --help # ayuda de cualquier comando
Generar piezas (g es la forma corta de generate):
| Comando | Corto | Qué crea |
|---|---|---|
ng g component <nombre> |
ng g c |
Un componente. El nombre puede ser una ruta (heroes/hero-card) |
ng g directive <nombre> |
ng g d |
Una directiva |
ng g pipe <nombre> |
ng g p |
Un pipe |
ng g service <nombre> |
ng g s |
Un servicio |
ng g guard <nombre> |
ng g g |
Un guard para proteger rutas |
ng g interceptor <nombre> |
Un interceptor de peticiones HTTP |
Banderas útiles:
| Bandera | Qué hace |
|---|---|
--dry-run |
Muestra qué archivos crearía, sin crear nada. Úsala si dudas |
--flat |
Crea los archivos en la carpeta actual, sin subcarpeta nueva |
--inline-style (-s) |
Pone los estilos dentro del archivo .ts |
--inline-template (-t) |
Pone el HTML dentro del archivo .ts |
--skip-tests |
No crea el archivo de pruebas |
Legacy:
ng g module <nombre>(ng g m) crea unNgModule. Con componentes standalone ya no hace falta para agrupar componentes.Legacy: los proyectos anteriores a Angular 20 generan los archivos con sufijo (
hero-card.component.ts,hero.service.ts). Hoy el CLI los crea sin sufijo (hero-card.ts).
Sintaxis de templates¶
| Sintaxis | Qué hace |
|---|---|
{{ heroName }} |
Muestra el valor en pantalla (interpolación) |
<input [value]="firstName"> |
Enlaza una propiedad del elemento con un valor de la clase |
<div [attr.role]="ariaRole"> |
Enlaza un atributo HTML |
<div [class.active]="isActive"> |
Pone la clase active sólo si la expresión es verdadera |
<div [style.width.px]="size"> |
Enlaza un estilo, con su unidad |
<button (click)="save($event)"> |
Escucha un evento y llama a un método |
<app-title [(title)]="name" /> |
Enlace en los dos sentidos: lee y escribe |
{{ heroName \| uppercase }} |
Pasa el valor por un pipe antes de mostrarlo |
{{ hero?.name }} |
Navegación segura: no falla si hero es null |
<input #search> |
Referencia local: search nombra ese elemento en el template |
El enlace en los dos sentidos es una forma corta de escribir esto:
<app-title [title]="name" (titleChange)="name = $event" />
Regla para recordar: corchetes [] = el dato entra al elemento,
paréntesis () = el evento sale, los dos juntos [()] = ida y vuelta.
Nada de esta tabla es legacy: es la sintaxis de siempre y sigue igual.
Legacy:
[ngClass]y[ngStyle]para poner clases y estilos.<div [ngClass]="{ active: isActive, disabled: isDisabled }"></div> <div [ngStyle]="{ color: textColor }"></div>Actual: los enlaces
classystyle, que no necesitan importar nada.<div [class.active]="isActive" [class.disabled]="isDisabled"></div> <div [class]="{ active: isActive, disabled: isDisabled }"></div> <div [style.color]="textColor"></div>
No es legacy: [(ngModel)] (de FormsModule) sigue siendo la forma de
enlazar un campo en los formularios por template.
<input [(ngModel)]="userName">
Control de flujo¶
@if (isAdmin) {
<button>Borrar</button>
} @else {
<p>No tienes permiso.</p>
}
<ul>
@for (hero of heroes; track hero.id) {
<li>{{ $index + 1 }}. {{ hero.name }}</li>
} @empty {
<li>No hay héroes.</li>
}
</ul>
@switch (status) {
@case ('FULL') { <span>Sano</span> }
@case ('INJURED') { <span>Herido</span> }
@default { <span>Desconocido</span> }
}
trackes obligatorio en@for. Le dice a Angular cómo reconocer cada elemento para reutilizarlo cuando la lista cambia. Usa algo único, como elid.@emptyse muestra cuando la lista no tiene elementos.
Variables disponibles dentro de @for:
| Variable | Valor |
|---|---|
$index |
Posición del elemento (empieza en 0) |
$count |
Total de elementos |
$first / $last |
true si es el primero / el último |
$even / $odd |
true si la posición es par / impar |
Legacy (obsoleto desde Angular 20): las directivas
*ngIf,*ngFory[ngSwitch], que además hay que importar desdeCommonModule.
Legacy Actual <section *ngIf="show">@if (show) { <section> }*ngIf="show; else other"+<ng-template #other>@if (show) { } @else { }<li *ngFor="let hero of heroes">@for (hero of heroes; track hero.id) { <li> }*ngFor="...; trackBy: trackById"track hero.id*ngFor="...; let i = index"$index[ngSwitch]+*ngSwitchCase@switch+@caseEl CLI migra un proyecto completo con
ng generate @angular/core:control-flow.
Comunicación entre componentes¶
export class HeroCard {
hero = input.required<Hero>() // dato que entra desde el padre
selected = output<Hero>() // evento que sale hacia el padre
select() {
this.selected.emit(this.hero())
}
}
<app-hero-card [hero]="geralt" (selected)="onSelect($event)" />
Ojo: un input() es un signal, así que se lee con paréntesis (this.hero()).
Legacy: los decoradores de propiedad. No están obsoletos, pero el código nuevo usa las funciones.
export class HeroCardComponent { @Input({ required: true }) hero!: Hero @Output() selected = new EventEmitter<Hero>() select() { this.selected.emit(this.hero) // sin paréntesis: no es un signal } }
Legacy Actual Para qué sirve @Input()input()Recibir un dato del componente padre @Output() x = new EventEmitter()output()Avisar al padre de que pasó algo @Input()+@Output()con sufijoChangemodel()Enlace en los dos sentidos ( [(x)])@ViewChild()/@ViewChildren()viewChild()/viewChildren()Obtener elementos del propio template @HostBinding()/@HostListener()host: { ... }en@ComponentPoner clases o escuchar eventos en la etiqueta del propio componente En el template no cambia nada: el padre escribe
[hero]y(selected)igual en los dos casos.
Inyección de dependencias¶
export class HeroList {
private heroService = inject(HeroService)
}
Legacy: pedir la dependencia como parámetro del constructor. No está obsoleto y funciona igual;
inject()es la forma recomendada hoy.export class HeroListComponent { constructor(private heroService: HeroService) {} }
Ciclo de vida¶
Métodos que Angular llama solo, en este orden, mientras un componente vive:
| Método | Cuándo se llama |
|---|---|
constructor |
Al crear la clase. Los datos del padre todavía no llegaron |
ngOnChanges |
Cada vez que cambia un dato de entrada. La primera vez, antes de ngOnInit |
ngOnInit |
Una sola vez, cuando ya llegaron los primeros datos de entrada |
ngDoCheck |
En cada revisión de cambios. Corre muy seguido: evita trabajo pesado |
ngAfterContentInit |
Una vez, cuando está listo el contenido que el padre proyectó (<ng-content>) |
ngAfterContentChecked |
Después de cada revisión de ese contenido |
ngAfterViewInit |
Una vez, cuando el template propio y los hijos ya están en pantalla |
ngAfterViewChecked |
Después de cada revisión del template propio |
ngOnDestroy |
Justo antes de destruir el componente. Aquí se limpia lo que se abrió |
Los que más se usan: ngOnInit para cargar datos y ngOnDestroy para
limpiar.
No son legacy: todos estos métodos siguen vigentes. Lo que cambió es que, con signals, varios se necesitan menos:
| Antes se usaba... | Con signals suele alcanzar... |
|---|---|
ngOnChanges para reaccionar a un @Input |
computed() o effect() sobre un input() |
ngOnDestroy para cerrar suscripciones |
takeUntilDestroyed() o DestroyRef |
ngAfterViewInit para tocar el DOM |
afterNextRender() |
Rutas¶
export const routes: Routes = [
{ path: '', component: Home },
{ path: 'heroes/:id', component: HeroDetail }, // :id es un parámetro
{ path: 'old-heroes', redirectTo: 'heroes' },
{
path: 'admin',
loadComponent: () => import('./admin/admin').then(m => m.Admin), // lazy loading
canActivate: [authGuard]
},
{ path: '**', component: NotFound } // cualquier otra ruta
]
Las rutas se registran en la configuración de la aplicación, y
<router-outlet /> marca el lugar del template donde se dibuja la ruta
activa.
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
}
Enlaces:
<a routerLink="/heroes">Héroes</a>
<a [routerLink]="['/heroes', hero.id]">Detalle</a>
<a [routerLink]="['/heroes']" [queryParams]="{ page: 1 }">Página 1</a>
<a [routerLink]="['/heroes']" fragment="top">Arriba</a>
<a routerLink="/heroes" routerLinkActive="active">Héroes</a>
routerLinkActive agrega la clase indicada cuando esa ruta es la actual; se
usa para resaltar el menú. Los enlaces no cambiaron: no hay forma legacy.
Legacy: registrar las rutas dentro de un
NgModuley hacer lazy loading de módulos enteros. No está obsoleto, pero pertenece a los proyectos con módulos.const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) } ] @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}Actual:
provideRouter(routes)yloadComponent.loadChildrensigue vigente cuando apunta a un archivo de rutas en lugar de a un módulo:loadChildren: () => import('./admin/admin.routes').then(m => m.routes).
Protección de rutas (guards)¶
Un guard es una función que responde «¿se puede pasar?» antes de entrar o salir de una ruta.
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService)
const router = inject(Router)
return auth.isLoggedIn() || router.createUrlTree(['/login'])
}
Lo que devuelve decide qué pasa: true deja pasar, false bloquea, y un
UrlTree redirige a otra ruta. También puede devolver una promesa o un
observable con cualquiera de esos valores.
| Tipo | Qué decide |
|---|---|
CanActivateFn |
Si se puede entrar a la ruta |
CanActivateChildFn |
Si se puede entrar a las rutas hijas |
CanDeactivateFn |
Si se puede salir. Útil cuando hay cambios sin guardar |
CanMatchFn |
Si la ruta cuenta como coincidencia. Evita incluso descargar su código |
ResolveFn |
No bloquea: trae datos antes de mostrar la ruta |
Legacy (obsoleto): guards escritos como clases, que además había que registrar en
providers.@Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private auth: AuthService, private router: Router) {} canActivate(): boolean | UrlTree { return this.auth.isLoggedIn() || this.router.createUrlTree(['/login']) } }
Legacy (clase que implementa...) Actual CanActivateCanActivateFnCanActivateChildCanActivateChildFnCanDeactivateCanDeactivateFnCanLoadCanMatchFnResolveResolveFnEn la ruta se usan igual:
canActivate: [AuthGuard]con la clase,canActivate: [authGuard]con la función.
Interceptores HTTP¶
Un interceptor revisa o modifica cada petición HTTP antes de que salga, y cada respuesta cuando vuelve.
export const languageInterceptor: HttpInterceptorFn = (req, next) => {
const language = inject(LanguageService).current()
return next(req.clone({ setHeaders: { 'Accept-Language': language } }))
}
providers: [provideHttpClient(withInterceptors([languageInterceptor]))]
Legacy: interceptores escritos como clases y registrados con el token
HTTP_INTERCEPTORS. Las clases no están obsoletas;HttpClientModulesí lo está, desde Angular 18.@Injectable() export class LanguageInterceptor implements HttpInterceptor { intercept(req: HttpRequest<unknown>, next: HttpHandler) { return next.handle(req) } } @NgModule({ imports: [HttpClientModule], providers: [{ provide: HTTP_INTERCEPTORS, useClass: LanguageInterceptor, multi: true }] }) export class AppModule {}
Pipes incluidos¶
Un pipe cambia cómo se muestra un dato, sin tocar el dato original.
| Pipe | Qué hace |
|---|---|
date |
Da formato a una fecha |
uppercase / lowercase |
Pasa todo el texto a mayúsculas / minúsculas |
titlecase |
Pone en mayúscula la primera letra de cada palabra |
currency |
Muestra un número como dinero |
number |
Da formato a un número (decimales, separadores) |
percent |
Muestra un número como porcentaje |
async |
Espera una promesa o un observable y muestra lo que llega. Cierra la suscripción solo |
json |
Muestra un objeto como JSON. Útil para depurar |
keyvalue |
Convierte un objeto en una lista de pares clave y valor para recorrerla |
slice |
Toma una parte de una lista o de un texto |
i18nPlural |
Elige un texto según una cantidad («1 mensaje», «3 mensajes») |
i18nSelect |
Elige un texto según un valor (por ejemplo, el idioma o el género) |
No son legacy: todos siguen vigentes. Sólo cambia cómo se importan: en un
componente standalone se importa cada pipe (imports: [DatePipe]) en lugar
de todo CommonModule. El pipe async se usa menos cuando el dato ya es un
signal, porque un signal se lee directo en el template ({{ hero().name }}).
Signals¶
Un signal es una caja que guarda un valor y avisa a quien lo usa cuando ese valor cambia. Así Angular sabe exactamente qué parte de la pantalla tiene que actualizar.
export class Counter {
count = signal(0) // signal que se puede escribir
doubleCount = computed(() => this.count() * 2) // se recalcula solo
increment() {
this.count.update(value => value + 1) // a partir del valor anterior
}
reset() {
this.count.set(0) // valor nuevo, sin mirar el anterior
}
}
| Qué | Para qué |
|---|---|
signal(0) |
Crea un signal con un valor inicial |
count() |
Lee el valor. Siempre con paréntesis |
count.set(10) |
Reemplaza el valor |
count.update(v => v + 1) |
Calcula el valor nuevo a partir del anterior |
computed(() => ...) |
Signal de sólo lectura que depende de otros y se recalcula solo |
effect(() => ...) |
Código que se vuelve a ejecutar cuando cambia un signal que lee |
Ojo con los objetos y arrays: no los modifiques por dentro. Si la referencia es la misma, el signal no avisa. Crea uno nuevo:
todos.update(list => list.map(todo => ({ ...todo, done: true })))
Effects. Corren al menos una vez y se repiten cuando cambia alguno de los signals que leen. Se crean en el constructor o como propiedad de la clase, y se destruyen solos junto con el componente.
constructor() {
effect((onCleanup) => {
const timer = setTimeout(() => console.log(this.count()), 1000)
onCleanup(() => clearTimeout(timer)) // limpia antes de volver a correr
})
}
- Úsalos para: registrar en consola o en analíticas, guardar en
localStorage, dibujar en un canvas, conectar librerías externas. - No los uses para copiar un signal en otro. Para eso está
computed; hacerlo con un effect produce ciclos y errores de detección de cambios.
Legacy: guardar el estado en propiedades comunes o en observables de RxJS (
BehaviorSubject) y mostrarlo con el pipeasync.export class CounterComponent { count$ = new BehaviorSubject(0) doubleCount$ = this.count$.pipe(map(value => value * 2)) increment() { this.count$.next(this.count$.value + 1) } }<p>{{ doubleCount$ | async }}</p>Ojo: RxJS no es legacy. Sigue siendo la herramienta para eventos en el tiempo (peticiones HTTP, búsquedas con espera, WebSockets). Lo que pasó a signals es el estado que se muestra en pantalla. Para pasar de uno a otro existen
toSignal()ytoObservable().Legacy:
signal.mutate(), que existió en las versiones de prueba de signals, se eliminó en Angular 17. Se usaupdate()creando un valor nuevo.
Vistas aplazables (@defer)¶
@defer deja para más tarde la descarga de un pedazo de pantalla, y te deja
elegir el momento.
@defer (on viewport; prefetch on idle) {
<app-calendar />
} @placeholder (minimum 500ms) {
<p>Aquí va el calendario</p>
} @loading (after 100ms; minimum 1s) {
<p>Cargando...</p>
} @error {
<p>No se pudo cargar el calendario.</p>
}
| Bloque | Qué muestra |
|---|---|
@defer |
El contenido pesado, cuando se cumple la condición |
@placeholder |
Lo que se ve antes de empezar a cargar |
@loading |
Lo que se ve mientras se descarga |
@error |
Lo que se ve si la descarga falla |
Cuándo se dispara la carga:
| Condición | Se dispara cuando... |
|---|---|
on idle |
El navegador queda libre. Es la opción por defecto |
on viewport |
El bloque entra en el área visible |
on interaction |
Alguien hace clic o escribe sobre el elemento |
on hover |
El mouse pasa por encima |
on immediate |
Apenas termina de dibujarse el resto de la página |
on timer(5s) |
Pasa el tiempo indicado |
when <condición> |
La expresión se vuelve verdadera |
- Se pueden combinar con
;. Alcanza con que se cumpla una. on interaction(greeting)yon hover(greeting)miran otro elemento, marcado con#greeting, en lugar del placeholder.prefetch on idledescarga el código por adelantado, sin dibujarlo, para que aparezca al instante cuando llegue el momento.
No hay forma legacy: @defer es nuevo (Angular 17). Antes, lo único que se
podía cargar más tarde era una ruta completa.
Glosario¶
| Término | En simple |
|---|---|
| Angular / AngularJS | Son dos cosas distintas. AngularJS es el antecesor y ya no tiene soporte |
| Componente | Una pieza de pantalla: una clase TypeScript, un template HTML y estilos opcionales |
| Componente standalone | Componente que declara él mismo lo que necesita, sin un módulo. Es lo normal hoy |
| Directiva | Cambia el comportamiento de un elemento HTML o de un componente |
| Pipe | Cambia cómo se muestra un dato, sin modificar el dato |
| Servicio | Clase con lógica o datos que se comparten entre varios componentes |
| Inyección de dependencias (DI) | La clase no crea lo que necesita: lo pide, y Angular se lo entrega ya creado |
| Guard | Portero de una ruta: decide si se puede entrar, cargar o salir |
| Interceptor | Revisa o modifica las peticiones HTTP y sus respuestas al pasar |
| Observable | Objeto que va entregando valores a lo largo del tiempo. Suscribirse es quedarse escuchándolos |
| RxJS | La librería de observables que usa Angular |
| Signal | Caja con un valor que avisa cuando cambia |
Módulo (NgModule) |
Legacy. Agrupador de componentes relacionados. Opcional desde los componentes standalone |
| Detección de cambios | El mecanismo que mantiene la pantalla igual al estado de la aplicación |
| Decorador | Etiqueta con @ que le dice a Angular qué es una clase. Ver «Qué es Angular» en la guía de estudio |
| Polyfill | Código que agrega a un navegador una función que no trae |
Convenciones de nombres: las de TypeScript están en el Módulo 4 de la
guía de estudio. Angular suma una: kebab-case (hero-card)
para nombres de archivo y selectores de componentes.