🅰️ Angular (v17+) Cheatsheet Completo 🅰️

Angular es un framework de desarrollo de aplicaciones web de código abierto, desarrollado por Google, para construir aplicaciones de una sola página (SPA) y aplicaciones móviles de alto rendimiento. Se enfoca en la arquitectura basada en componentes, el tipado estático (TypeScript) y la reactividad (RxJS).


1. 🌟 Conceptos Clave


2. 🛠️ Configuración Inicial (Angular CLI)

Angular CLI (Command Line Interface) es la herramienta oficial para inicializar, desarrollar y mantener aplicaciones Angular.

  1. Instalación Global de Angular CLI:
    npm install -g @angular/cli
    # o
    yarn global add @angular/cli
    ```2.  **Crear un Nuevo Proyecto Angular:**
    ```bash
    ng new my-angular-app --standalone --routing --style=scss # '--standalone' crea un proyecto con componentes standalone por defecto
    # o
    ng new my-angular-app # Si quieres un proyecto basado en NgModules (legacy)
    cd my-angular-app
  2. Iniciar el Servidor de Desarrollo:
    ng serve --open # Compila la app y la abre en el navegador (usualmente http://localhost:4200)
  3. Generar Componentes, Servicios, etc.:
    ng generate component my-new-component # Crea un componente
    ng g s my-new-service # Crea un servicio (shortcut)
    ng g route my-feature # Genera un componente con una ruta configurada (standalone)
    # y muchos más
  4. Construir para Producción:
    ng build --configuration production # Compila la app para producción (optimizado)

3. 🧩 Componentes (Standalone - Preferido en v17+)

Un componente combina su lógica, su vista y sus estilos.

// src/app/counter/counter.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; // Necesario para directivas comunes como *ngIf, *ngFor

@Component({
  selector: 'app-counter', // Etiqueta HTML para usar el componente
  standalone: true, // ¡Importante para componentes standalone!
  imports: [CommonModule], // Importa módulos/componentes/directivas que este componente usa
  template: `
    <div class="counter-card">
      <h2>Contador: {{ count }}</h2>
      <p *ngIf="count % 2 === 0" class="even">El número es par</p>
      <button (click)="increment()">Incrementar</button>
      <button (click)="decrement()">Decrementar</button>
      <button (click)="reset.emit()">Resetear</button>
    </div>
  `,
  styles: [`
    .counter-card {
      border: 1px solid #ccc;
      padding: 20px;
      margin: 10px;
      text-align: center;
    }
    .even { color: green; }
  `]
})
export class CounterComponent implements OnInit {
  @Input() initialValue: number = 0; // Propiedad de entrada
  @Output() reset = new EventEmitter<void>(); // Evento de salida

  count: number = 0;

  ngOnInit(): void {
    this.count = this.initialValue;
  }

  increment(): void {
    this.count++;
  }

  decrement(): void {
    this.count--;
  }
}

Uso de un Componente Standalone:

// src/app/app.component.ts
import { Component } from '@angular/core';
import { CounterComponent } from './counter/counter.component'; // Importa el componente

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CounterComponent], // Declara el componente que vas a usar en este template
  template: `
    <h1>Mi Aplicación Angular</h1>
    <app-counter [initialValue]="10" (reset)="onResetCounter()"></app-counter>
    <app-counter initialValue="5"></app-counter> {/* Si el input es un string */}
  `,
  styles: []
})
export class AppComponent {
  onResetCounter(): void {
    console.log('El contador ha sido reseteado!');
  }
}

4. 🔗 Data Binding (Enlace de Datos)

Angular soporta varias formas de enlazar datos:


5. 🏗️ Directivas Comunes

5.1. Directivas Estructurales (Modifican el DOM)

5.2. Directivas de Atributo (Cambian Apariencia/Comportamiento)


6. 💉 Servicios e Inyección de Dependencias

// src/app/user.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; // Ejemplo con RxJS

@Injectable({
  providedIn: 'root' // Servicio singleton global
})
export class UserService {
  private users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  getUsers(): Observable<any[]> {
    // Simula una llamada API
    return of(this.users);
  }

  getUserById(id: number): Observable<any | undefined> {
    return of(this.users.find(user =&gt; user.id === id));
  }
}

Uso de un Servicio en un Componente:

// src/app/user-list/user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from '../user.service'; // Importa el servicio

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h3>Lista de Usuarios</h3>
    <ul *ngIf="users.length > 0; else noUsers">
      <li *ngFor="let user of users">{{ user.name }}</li>
    </ul>
    <ng-template #noUsers>
      <p>No hay usuarios.</p>
    </ng-template>
  `
})
export class UserListComponent implements OnInit {
  users: any[] = [];

  constructor(private userService: UserService) { } // Inyección de dependencia por constructor

  ngOnInit(): void {
    this.userService.getUsers().subscribe(data =&gt; {
      this.users = data;
    });
  }
}

7. 🗺️ Routing (Enrutamiento)

Permite navegar entre diferentes “páginas” de tu SPA.

// src/app/app.routes.ts (o app-routing.module.ts si no es standalone)
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { AuthGuard } from './auth.guard'; // Ejemplo de guard

export const routes: Routes = [
  { path: '', component: HomeComponent, title: 'Home Page' }, // Ruta raíz
  { path: 'users/:id', component: UserDetailComponent, canActivate: [AuthGuard] }, // Ruta con parámetro y guard
  { path: 'products', loadChildren: () =&gt; import('./products/products.routes').then(m =&gt; m.PRODUCT_ROUTES) }, // Lazy loading de rutas
  { path: '**', component: NotFoundComponent }, // Ruta wildcard (404)
];

Configuración en main.ts (Standalone):

// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routes'; // Importa tus rutas

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes) // Provee el enrutamiento
  ]
}).catch(err =&gt; console.error(err));

Navegación en el Template:

Navegación Programática (en Componente):

import { Router, ActivatedRoute } from '@angular/router';

constructor(private router: Router, private route: ActivatedRoute) { }

goToHome(): void {
  this.router.navigate(['/home']);
}

goToUserDetail(id: number): void {
  this.router.navigate(['/users', id]);
}

// Para obtener parámetros de ruta:
ngOnInit(): void {
  this.route.paramMap.subscribe(params =&gt; {
    const userId = params.get('id');
    console.log('ID de usuario:', userId);
  });
  // Para query parameters:
  this.route.queryParamMap.subscribe(params =&gt; {
    const sort = params.get('sort');
    console.log('Parámetro de ordenación:', sort);
  });
}

8. 📝 Formularios

Angular ofrece dos enfoques para formularios:

8.1. Formularios Dirigidos por Plantillas (Template-Driven Forms)

// Importar en tu componente standalone o NgModule
// import { FormsModule } from '@angular/forms';
// @Component({ standalone: true, imports: [FormsModule], ... })

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-template-form',
  standalone: true,
  imports: [CommonModule, FormsModule], // Importa FormsModule
  template: `
    <h3>Formulario Dirigido por Plantilla</h3>
    <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)">
      <label>Nombre:
        <input type="text" name="name" [(ngModel)]="user.name" required minlength="3" #nameField="ngModel">
        <div *ngIf="nameField.invalid && (nameField.dirty || nameField.touched)">
          <div *ngIf="nameField.errors?.['required']">Nombre es requerido.</div>
          <div *ngIf="nameField.errors?.['minlength']">Nombre debe tener al menos 3 caracteres.</div>
        </div>
      </label>
      <br>
      <label>Email:
        <input type="email" name="email" [(ngModel)]="user.email" email>
      </label>
      <br>
      <button type="submit" [disabled]="myForm.invalid">Enviar</button>
    </form>
    <p>Formulario válido: {{ myForm.valid }}</p>
  `
})
export class TemplateFormComponent {
  user = {
    name: '',
    email: ''
  };

  onSubmit(formValue: any): void {
    console.log('Formulario enviado:', formValue);
  }
}

8.2. Formularios Reactivos (Reactive Forms)

// Importar en tu componente standalone o NgModule
// import { ReactiveFormsModule } from '@angular/forms';
// @Component({ standalone: true, imports: [ReactiveFormsModule], ... })

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-reactive-form',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule], // Importa ReactiveFormsModule
  template: `
    <h3>Formulario Reactivo</h3>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <label>Usuario:
        <input type="text" formControlName="username">
        <div *ngIf="usernameControl.invalid && usernameControl.touched">
          <div *ngIf="usernameControl.errors?.['required']">Usuario es requerido.</div>
          <div *ngIf="usernameControl.errors?.['minlength']">Mínimo 5 caracteres.</div>
        </div>
      </label>
      <br>
      <label>Contraseña:
        <input type="password" formControlName="password">
        <div *ngIf="passwordControl.invalid && passwordControl.touched">
          <div *ngIf="passwordControl.errors?.['required']">Contraseña es requerida.</div>
        </div>
      </label>
      <br>
      <button type="submit" [disabled]="loginForm.invalid">Login</button>
    </form>
    <p>Formulario válido: {{ loginForm.valid }}</p>
    <p>Valores: {{ loginForm.value | json }}</p>
  `
})
export class ReactiveFormComponent implements OnInit {
  loginForm!: FormGroup; // ! para asegurar que se inicializa en ngOnInit

  constructor(private fb: FormBuilder) { } // FormBuilder es una utilidad para crear FormGroups/FormControls

  ngOnInit(): void {
    this.loginForm = this.fb.group({
      username: new FormControl('', [Validators.required, Validators.minLength(5)]),
      password: new FormControl('', Validators.required)
    });
  }

  // Getter para facilitar el acceso a los controles en el template
  get usernameControl() {
    return this.loginForm.get('username') as FormControl;
  }

  get passwordControl() {
    return this.loginForm.get('password') as FormControl;
  }

  onSubmit(): void {
    if (this.loginForm.valid) {
      console.log('Formulario de login enviado:', this.loginForm.value);
    }
  }
}

9. ♻️ Lifecycle Hooks (Ciclo de Vida del Componente)

Métodos que Angular llama en diferentes etapas del ciclo de vida de un componente.

import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-lifecycle',
  standalone: true,
  template: `<p>Componente de ciclo de vida</p>`
})
export class LifecycleComponent implements OnInit, OnDestroy, OnChanges {
  @Input() data: string = '';

  constructor() {
    console.log('1. Constructor - Componente inicializado.');
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['data']) {
      console.log('2. ngOnChanges - Propiedad "data" ha cambiado:', changes['data'].currentValue);
    }
  }

  ngOnInit(): void {
    console.log('3. ngOnInit - Componente listo, propiedades de entrada establecidas.');
    // Aquí se suele hacer la carga inicial de datos
  }

  ngOnDestroy(): void {
    console.log('8. ngOnDestroy - Componente a punto de ser destruido. Liberar recursos aquí.');
    // Desuscribirse de observables, limpiar timers, etc.
  }

  // Otros hooks (ngDoCheck, ngAfterContentInit, etc.)
  // se ejecutan después de ngOnInit y antes de ngOnDestroy
}

10. ⚡ RxJS (Programación Reactiva)

Angular utiliza RxJS para manejar eventos asíncronos y flujos de datos.

import { Observable, from, of, map, filter, take, tap } from 'rxjs';

// Crear un Observable
const myObservable = new Observable<number>(subscriber =&gt; {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() =&gt; {
    subscriber.next(4);
    subscriber.complete(); // Indica que no hay más valores
  }, 1000);
});

// Suscribirse a un Observable
myObservable.subscribe({
  next: value =&gt; console.log('RxJS Value:', value),
  error: err =&gt; console.error('RxJS Error:', err),
  complete: () =&gt; console.log('RxJS Completed!')
});

// Uso de operadores con pipe
of(1, 2, 3, 4, 5) // Observable de 5 números
  .pipe(
    filter(num =&gt; num % 2 === 0), // Solo números pares
    map(num =&gt; num * 10),       // Multiplicar por 10
    take(1),                    // Tomar solo el primer resultado
    tap(value =&gt; console.log('Tap (before subscribe):', value)) // Efecto secundario sin alterar el flujo
  )
  .subscribe(finalValue =&gt; console.log('Final Processed Value:', finalValue)); // 20

11. 🧪 Testing

Angular CLI viene con soporte para Karma (para pruebas de unidad) y Protractor (para pruebas E2E - ahora con Cypress/Playwright). Jest y Cypress son alternativas populares.


12. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de los aspectos más importantes de Angular (versión 17 y posterior), lo que te permitirá construir aplicaciones robustas, escalables y de alto rendimiento. ¡Las futuras versiones de Angular construirán sobre estos sólidos fundamentos!