Skip to main content

angular

Use when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change detection, or writing TestBed component tests.

Datos de origen

Repositorio
kid-sid/claude-spellbook
Última actividad en el origen
6 de mayo de 2026 a las 13:43
Idioma detectado de SKILL.md
inglés
Estrellas
192
Forks
16

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
angular
description
Use when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change detection, or writing TestBed component tests.
# Angular Patterns Modern Angular (v17+) conventions using standalone components, signals, and typed forms. ## When to Activate - Creating or refactoring Angular components, services, or directives - Choosing between signals, RxJS, or NgRx for state - Setting up routing with guards, resolvers, or lazy loading - Building reactive or template-driven forms with validation - Writing Angular tests (TestBed, jasmine, or jest) - Configuring HttpClient with interceptors or typed responses - Optimizing change detection with `OnPush` --- ## Project Structure ``` src/ ├── app/ │ ├── core/ # Singleton services, interceptors, guards (provided in root) │ │ ├── services/ │ │ ├── interceptors/ │ │ └── guards/ │ ├── shared/ # Reusable standalone components, pipes, directives │ │ ├── components/ │ │ ├── pipes/ │ │ └── directives/ │ ├── features/ # Feature modules — each is a route subtree │ │ ├── users/ │ │ │ ├── user-list/ │ │ │ ├── user-detail/ │ │ │ └── users.routes.ts │ │ └── orders/ │ ├── app.component.ts │ ├── app.config.ts # bootstrapApplication providers │ └── app.routes.ts ``` --- ## Standalone Components (Angular 17+) No NgModule required. Default for all new components. ```typescript import { Component, inject, signal, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterLink } from '@angular/router'; import { UserService } from '../core/services/user.service'; @Component({ selector: 'app-user-list', standalone: true, imports: [CommonModule, RouterLink], // import what you use changeDetection: ChangeDetectionStrategy.OnPush, template: ` <ul> @for (user of users(); track user.id) { <li> <a [routerLink]="['/users', user.id]">{{ user.name }}</a> </li> } @empty { <li>No users found.</li> } </ul> `, }) export class UserListComponent { private userService = inject(UserService); // inject() instead of constructor DI users = this.userService.users; // signal from service } ``` ### New Control Flow (Angular 17+) Prefer `@if`, `@for`, `@switch` over `*ngIf`, `*ngFor`: ```html @if (isLoading()) { <app-spinner /> } @else if (error()) { <app-error [message]="error()" /> } @else { <app-data-table [rows]="data()" /> } @for (item of items(); track item.id) { <app-item [item]="item" /> } @empty { <p>Nothing here.</p> } @switch (status()) { @case ('active') { <span class="green">Active</span> } @case ('inactive') { <span class="red">Inactive</span> } @default { <span>Unknown</span> } } ``` --- ## Signals Signals replace `BehaviorSubject` for synchronous local and service state. ```typescript import { signal, computed, effect } from '@angular/core'; // Writable signal const count = signal(0); count.set(5); count.update(n => n + 1); count(); // read — always call as a function // Computed — automatically tracks dependencies const doubled = computed(() => count() * 2); // Effect — runs when dependencies change (use sparingly) effect(() => { console.log('count changed:', count()); }); ``` ### Signals in Services ```typescript @Injectable({ providedIn: 'root' }) export class UserService { private http = inject(HttpClient); // Private writable, public readonly private _users = signal<User[]>([]); readonly users = this._users.asReadonly(); readonly activeUsers = computed(() => this._users().filter(u => u.active) ); load() { this.http.get<User[]>('/api/users').subscribe(users => { this._users.set(users); }); } add(user: User) { this._users.update(list => [...list, user]); } } ``` ### toSignal / toObservable — bridging signals ↔ RxJS ```typescript import { toSignal, toObservable } from '@angular/core/rxjs-interop'; // Convert Observable to Signal (use in components instead of async pipe) readonly users = toSignal(this.http.get<User[]>('/api/users'), { initialValue: [] as User[], }); // Convert Signal to Observable (pass to RxJS operators) const count$ = toObservable(this.count); count$.pipe(debounceTime(300)).subscribe(...); ``` --- ## Dependency Injection ```typescript // Provided in root (singleton for the whole app) @Injectable({ providedIn: 'root' }) export class AuthService { ... } // Provided in a specific component subtree (new instance per component) @Component({ providers: [LocalCartService], // scoped to this component and its children }) // inject() function — preferred over constructor injection export class MyComponent { private auth = inject(AuthService); private router = inject(Router); } // Injection tokens for non-class values export const API_URL = new InjectionToken<string>('API_URL'); // app.config.ts export const appConfig: ApplicationConfig = { providers: [ { provide: API_URL, useValue: 'https://api.example.com' }, provideHttpClient(withInterceptors([authInterceptor])), provideRouter(routes), ], }; // Usage private apiUrl = inject(API_URL); ``` --- ## HttpClient ```typescript @Injectable({ providedIn: 'root' }) export class UserService { private http = inject(HttpClient); private apiUrl = inject(API_URL); getUsers(): Observable<User[]> { return this.http.get<User[]>(`${this.apiUrl}/users`); } createUser(data: CreateUserDto): Observable<User> { return this.http.post<User>(`${this.apiUrl}/users`, data); } updateUser(id: string, data: Partial<User>): Observable<User> { return this.http.patch<User>(`${this.apiUrl}/users/${id}`, data); } deleteUser(id: string): Observable<void> { return this.http.delete<void>(`${this.apiUrl}/users/${id}`); } } ``` ### Functional Interceptors (Angular 15+) ```typescript // core/interceptors/auth.interceptor.ts import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; export const authInterceptor: HttpInterceptorFn = (req, next) => { const auth = inject(AuthService); const token = auth.getToken(); if (!token) return next(req); return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` }, })); }; // core/interceptors/error.interceptor.ts export const errorInterceptor: HttpInterceptorFn = (req, next) => { return next(req).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401) inject(AuthService).logout(); if (error.status === 0) console.error('Network error'); return throwError(() => error); }), ); }; // app.config.ts provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])) ``` --- ## Routing ```typescript // app.routes.ts import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, // Lazy-loaded feature route { path: 'users', loadChildren: () => import('./features/users/users.routes').then(m => m.USERS_ROUTES), }, // Protected route { path: 'admin', canActivate: [authGuard], loadChildren: () => import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES), }, { path: '**', component: NotFoundComponent }, ]; // features/users/users.routes.ts export const USERS_ROUTES: Routes = [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailComponent, resolve: { user: userResolver } }, ]; ``` ### Guards and Resolvers ```typescript // Functional guard (Angular 14+) export const authGuard: CanActivateFn = (route, state) => { const auth = inject(AuthService); const router = inject(Router); if (auth.isLoggedIn()) return true; return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); }; // Functional resolver export const userResolver: ResolveFn<User> = (route) => { return inject(UserService).getUser(route.paramMap.get('id')!); }; // Component reads resolved data export class UserDetailComponent { private route = inject(ActivatedRoute); user = toSignal(this.route.data.pipe(map(d => d['user'] as User))); } ``` --- ## Forms ### Reactive Forms (preferred for complex forms) ```typescript import { FormBuilder, Validators, AbstractControl } from '@angular/forms'; @Component({ imports: [ReactiveFormsModule], template: ` <form [formGroup]="form" (ngSubmit)="submit()"> <input formControlName="email" /> @if (email.invalid && email.touched) { <span>{{ emailError() }}</span> } <input type="password" formControlName="password" /> <button type="submit" [disabled]="form.invalid">Save</button> </form> `, }) export class UserFormComponent { private fb = inject(FormBuilder); form = this.fb.group({ email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]], }); get email() { return this.form.controls.email; } emailError = computed(() => { if (this.email.hasError('required')) return 'Email is required'; if (this.email.hasError('email')) return 'Invalid email format'; return ''; }); submit() { if (this.form.invalid) return; console.log(this.form.getRawValue()); } } ``` ### Custom Validator ```typescript function noSpaces(control: AbstractControl) { return control.value?.includes(' ') ? { noSpaces: 'Username cannot contain spaces' } : null; } // Async validator (e.g. check username availability) function uniqueUsername(userService: UserService): AsyncValidatorFn { return (control) => userService.checkUsername(control.value).pipe( map(taken => taken ? { usernameTaken: true } : null), catchError(() => of(null)), ); } ``` ### Template-Driven Forms (simple forms only) ```typescript @Component({ imports: [FormsModule], template: ` <form #f="ngForm" (ngSubmit)="submit(f)">
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub