Saltar a contenido

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 un NgModule. 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 class y style, 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> }
}
  • track es obligatorio en @for. Le dice a Angular cómo reconocer cada elemento para reutilizarlo cuando la lista cambia. Usa algo único, como el id.
  • @empty se 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, *ngFor y [ngSwitch], que además hay que importar desde CommonModule.

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 + @case

El 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 sufijo Change model() Enlace en los dos sentidos ([(x)])
@ViewChild() / @ViewChildren() viewChild() / viewChildren() Obtener elementos del propio template
@HostBinding() / @HostListener() host: { ... } en @Component Poner 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 NgModule y 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) y loadComponent. loadChildren sigue 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
CanActivate CanActivateFn
CanActivateChild CanActivateChildFn
CanDeactivate CanDeactivateFn
CanLoad CanMatchFn
Resolve ResolveFn

En 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; HttpClientModule sí 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 pipe async.

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() y toObservable().

Legacy: signal.mutate(), que existió en las versiones de prueba de signals, se eliminó en Angular 17. Se usa update() 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) y on hover(greeting) miran otro elemento, marcado con #greeting, en lugar del placeholder.
  • prefetch on idle descarga 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.