Saltar a contenido

Angular - Guía de Entorno

Guía para configurar el entorno de desarrollo Angular para trabajar con Claude Code.

Resumen de Capacidades

Capacidad Herramientas
Framework Angular 17+, Angular CLI
Build esbuild, Webpack
State NgRx, Signals
Testing Jest, Jasmine, Karma, Cypress
Styling Angular Material, Tailwind
Type Safety TypeScript estricto

Instalación

Node.js y npm/pnpm

# Windows
winget install OpenJS.NodeJS.LTS

# macOS
brew install node@22

# Verificar
node --version   # 18.x+ (requerido)
npm --version    # 10.x+

Angular CLI

# Instalar globalmente
npm install -g @angular/cli

# Verificar
ng version

# Output esperado:
#      _                      _                 ____ _     ___
#     / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
#    / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
#   / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
#  /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
#                 |___/
#
# Angular CLI: 17.x.x
# Node: 22.x.x

Crear Proyectos

Nuevo Proyecto Angular

# Crear proyecto
ng new my-angular-app

# Opciones recomendadas:
# - Stylesheet format: SCSS
# - SSR/SSG: Yes (si necesitas SEO)
# - Do you want to enable Server-Side Rendering: Yes

cd my-angular-app
ng serve

Opciones de Creación

# Proyecto standalone (sin módulos - Angular 17+)
ng new my-app --standalone

# Con routing
ng new my-app --routing

# Con SCSS
ng new my-app --style=scss

# Sin tests (no recomendado)
ng new my-app --skip-tests

# Minimal (sin archivos extras)
ng new my-app --minimal

Estructura de Proyecto

my-angular-app/
├── src/
│   ├── app/
│   │   ├── components/
│   │   │   └── header/
│   │   │       ├── header.component.ts
│   │   │       ├── header.component.html
│   │   │       ├── header.component.scss
│   │   │       └── header.component.spec.ts
│   │   ├── pages/
│   │   │   ├── home/
│   │   │   └── about/
│   │   ├── services/
│   │   │   └── api.service.ts
│   │   ├── models/
│   │   │   └── user.model.ts
│   │   ├── guards/
│   │   ├── interceptors/
│   │   ├── pipes/
│   │   ├── directives/
│   │   ├── app.component.ts
│   │   ├── app.config.ts
│   │   └── app.routes.ts
│   ├── assets/
│   ├── environments/
│   │   ├── environment.ts
│   │   └── environment.prod.ts
│   ├── index.html
│   ├── main.ts
│   └── styles.scss
├── angular.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.spec.json
└── package.json

Generación de Código (Schematics)

# Componentes
ng generate component components/header
ng g c components/header --standalone    # Standalone
ng g c pages/home --skip-tests           # Sin test

# Servicios
ng generate service services/api
ng g s services/auth

# Pipes
ng generate pipe pipes/date-format

# Directives
ng generate directive directives/highlight

# Guards
ng generate guard guards/auth

# Interceptors
ng generate interceptor interceptors/auth

# Módulos (legacy)
ng generate module features/users --routing

Componentes Standalone (Angular 17+)

// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, HeaderComponent],
  template: `
    <app-header />
    <main>
      <router-outlet />
    </main>
  `,
  styles: [`
    main {
      padding: 1rem;
    }
  `]
})
export class AppComponent {
  title = 'my-app';
}

Signals (Angular 17+)

import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Count: {{ count() }}</p>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+</button>
    <button (click)="decrement()">-</button>
  `
})
export class CounterComponent {
  // Signals
  count = signal(0);

  // Computed signals
  doubled = computed(() => this.count() * 2);

  constructor() {
    // Effect (side effects)
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }

  increment() {
    this.count.update(c => c + 1);
  }

  decrement() {
    this.count.set(this.count() - 1);
  }
}

Servicios e Inyección de Dependencias

// services/api.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private http = inject(HttpClient);
  private baseUrl = '/api';

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(`${this.baseUrl}/users`);
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.baseUrl}/users/${id}`);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(`${this.baseUrl}/users`, user);
  }
}
// Uso en componente
import { Component, inject, OnInit } from '@angular/core';
import { ApiService, User } from '../services/api.service';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (users$ | async; as users) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }}</li>
        }
      </ul>
    } @else {
      <p>Loading...</p>
    }
  `
})
export class UsersComponent implements OnInit {
  private apiService = inject(ApiService);
  users$!: Observable<User[]>;

  ngOnInit() {
    this.users$ = this.apiService.getUsers();
  }
}

Routing

// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './guards/auth.guard';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./pages/home/home.component')
      .then(m => m.HomeComponent)
  },
  {
    path: 'about',
    loadComponent: () => import('./pages/about/about.component')
      .then(m => m.AboutComponent)
  },
  {
    path: 'dashboard',
    canActivate: [authGuard],
    loadChildren: () => import('./features/dashboard/dashboard.routes')
      .then(m => m.DASHBOARD_ROUTES)
  },
  {
    path: '**',
    redirectTo: ''
  }
];
// guards/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isAuthenticated()) {
    return true;
  }

  return router.createUrlTree(['/login']);
};

State Management (NgRx)

# Instalar NgRx
ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools
// store/user/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User } from '../../models/user.model';

export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User] Load Users Failure',
  props<{ error: string }>()
);
// store/user/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';

export interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
}

const initialState: UserState = {
  users: [],
  loading: false,
  error: null
};

export const userReducer = createReducer(
  initialState,
  on(UserActions.loadUsers, state => ({
    ...state,
    loading: true
  })),
  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    error,
    loading: false
  }))
);

Testing

Unit Tests (Jest)

# Configurar Jest
ng add @angular-builders/jest
// components/counter/counter.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should increment count', () => {
    expect(component.count()).toBe(0);
    component.increment();
    expect(component.count()).toBe(1);
  });
});
# Ejecutar tests
ng test
ng test --watch=false --browsers=ChromeHeadless
ng test --code-coverage

E2E Tests (Cypress)

ng add @cypress/schematic
// cypress/e2e/home.cy.ts
describe('Home Page', () => {
  it('should display welcome message', () => {
    cy.visit('/');
    cy.contains('Welcome');
  });

  it('should navigate to about', () => {
    cy.visit('/');
    cy.get('[data-cy="nav-about"]').click();
    cy.url().should('include', '/about');
  });
});

Angular Material

# Instalar
ng add @angular/material

# Usar componentes
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';

Comandos que Claude Code Ejecutará

# Proyecto
ng new my-app
ng serve
ng build
ng build --configuration=production

# Generación
ng generate component components/name
ng generate service services/name
ng generate guard guards/name

# Testing
ng test
ng e2e

# Linting
ng lint

# Actualización
ng update
ng update @angular/core @angular/cli

VS Code Extensions

code --install-extension Angular.ng-template
code --install-extension johnpapa.Angular2
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension nrwl.angular-console

Verificación del Entorno

#!/bin/bash
echo "=== Verificación Entorno Angular ==="

echo -e "\n--- Runtime ---"
node --version
npm --version

echo -e "\n--- Angular CLI ---"
ng version

echo -e "\n=== Verificación Completa ==="

Recursos