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.

معلومات المصدر

المستودع
kid-sid/claude-spellbook
آخر نشاط في المصدر
٦ مايو ٢٠٢٦ في ١٣:٤٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٩٠
التفرعات
١٦

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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)">
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub