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)¶
// 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)¶
// 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);
});
});
E2E Tests (Cypress)¶
// 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 ==="