62. Лучшие практики
65. Лучшие практики Angular 🏆
Заголовок раздела «65. Лучшие практики Angular 🏆»Привет! Яша здесь. Последний урок курса! Сегодня соберём всё лучшее, что мы знаем об Angular, в один чеклист. Это production-ready паттерны от реальных проектов 🚀
Архитектура: Smart/Dumb компоненты
Заголовок раздела «Архитектура: Smart/Dumb компоненты»// ✅ Smart (Container) компонент — знает о стейте@Component({ selector: 'app-users-page', template: ` <app-users-list [users]="users$ | async" [loading]="loading$ | async" (deleteUser)="onDelete($event)" (selectUser)="onSelect($event)"> </app-users-list> `, changeDetection: ChangeDetectionStrategy.OnPush,})export class UsersPageComponent { users$ = this.usersFacade.users$; loading$ = this.usersFacade.loading$;
constructor(private usersFacade: UsersFacade) {}
onDelete(id: string): void { this.usersFacade.deleteUser(id); } onSelect(id: string): void { this.usersFacade.selectUser(id); }}
// ✅ Dumb (Presentational) компонент — только отображение@Component({ selector: 'app-users-list', template: ` @if (loading) { <app-spinner /> } @for (user of users; track user.id) { <app-user-card [user]="user" (delete)="deleteUser.emit(user.id)" (select)="selectUser.emit(user.id)"> </app-user-card> } `, changeDetection: ChangeDetectionStrategy.OnPush, // ← ВСЕГДА!})export class UsersListComponent { @Input() users: User[] | null = null; @Input() loading: boolean | null = false; @Output() deleteUser = new EventEmitter<string>(); @Output() selectUser = new EventEmitter<string>();}Naming Conventions (Angular Style Guide)
Заголовок раздела «Naming Conventions (Angular Style Guide)»Файлы:users.component.ts ✅ feature-name.type.tsusers.component.htmlusers.component.scssusers.component.spec.tsusers.service.tsusers.module.ts (если не standalone)users.model.ts или user.model.tsusers.facade.tscreate-user.component.ts ✅ kebab-case для составных
Классы:UsersComponent ✅ PascalCase + типUsersServiceUsersModuleUsersFacadeFeatureAuthModuleCreateUserDTO
Переменные:users$ ✅ $ для ObservableisLoading ✅ is/has для booleancurrentUser ✅ camelCaseMAX_RETRIES = 3 ✅ UPPER_SNAKE для константFeature Modules: правильная структура
Заголовок раздела «Feature Modules: правильная структура»src/├── app/│ ├── core/ ← singleton сервисы, guards, interceptors│ │ ├── auth/│ │ │ ├── auth.service.ts│ │ │ ├── auth.guard.ts│ │ │ └── auth.interceptor.ts│ │ └── core.module.ts ← импортируется ОДИН раз в AppModule│ ││ ├── shared/ ← переиспользуемые компоненты/pipes/directives│ │ ├── components/│ │ │ ├── button/│ │ │ └── modal/│ │ ├── pipes/│ │ ├── directives/│ │ └── index.ts ← barrel export!│ ││ ├── features/ ← lazy-loaded feature модули│ │ ├── users/│ │ │ ├── components/│ │ │ ├── pages/│ │ │ ├── services/│ │ │ ├── store/│ │ │ ├── users.routes.ts│ │ │ └── index.ts│ │ └── products/│ ││ └── app.component.tsBarrel exports: index.ts
Заголовок раздела «Barrel exports: index.ts»// shared/index.ts — единая точка импортаexport { ButtonComponent } from './components/button/button.component';export { InputComponent } from './components/input/input.component';export { ModalComponent } from './components/modal/modal.component';export { LoadingSpinnerComponent } from './components/loading-spinner/loading-spinner.component';export { TruncatePipe } from './pipes/truncate.pipe';export { ClickOutsideDirective } from './directives/click-outside.directive';
// Использование:// import { ButtonComponent, ModalComponent } from '@/shared';// Вместо:// import { ButtonComponent } from '../../../shared/components/button/button.component';// import { ModalComponent } from '../../../shared/components/modal/modal.component';SOLID принципы в Angular
Заголовок раздела «SOLID принципы в Angular»// S — Single Responsibility// ✅ Сервис делает одно дело@Injectable({ providedIn: 'root' })export class UserApiService { getUsers() { return this.http.get<User[]>('/api/users'); } // ТОЛЬКО HTTP. Не смешивай с форматированием, кэшем, логикой.}
// O — Open/Closed// ✅ Расширяй без изменения через абстракцииabstract class Validator { abstract validate(value: unknown): ValidationErrors | null;}
// L — Liskov Substitution// ✅ Подкласс не нарушает контракт базового класса
// I — Interface Segregation// ✅ Небольшие интерфейсыinterface Readable { read(): Observable<unknown>; }interface Writable { write(data: unknown): Observable<void>; }// Не делай один огромный interface Repository
// D — Dependency Inversion// ✅ Зависи от абстракций, не от конкретных классовabstract class NotificationService { abstract notify(message: string): void;}
@Injectable()class EmailNotificationService extends NotificationService { notify(message: string): void { /* email */ }}
@Component({ providers: [ { provide: NotificationService, useClass: EmailNotificationService } ]})export class OrderComponent { constructor(private notifications: NotificationService) {} // ← абстракция!}Performance Checklist
Заголовок раздела «Performance Checklist»// ✅ Change Detection// - OnPush ВЕЗДЕ// - async pipe вместо ручного subscribe// - trackBy во всех *ngFor / @for// - Иммутабельные данные
// ✅ Bundle size// - Lazy loading feature modules// - @defer для off-screen компонентов// - NgOptimizedImage для изображений// - Удали неиспользуемые зависимости
// ✅ Memory leaks// - takeUntilDestroyed() или takeUntil(destroy$)// - ngOnDestroy для отписки// - Проверь: componentRef.destroy()
// ✅ Типичные анти-паттерны// ❌ subscribe() без unsubscribe// ❌ Методы в шаблонах (вместо pure pipes)// ❌ ChangeDetectionStrategy.Default// ❌ * import { everything } from 'lodash'// ❌ Огромные компоненты (>300 строк — признак проблемы)Безопасность: XSS, Sanitization, CSP
Заголовок раздела «Безопасность: XSS, Sanitization, CSP»// Angular автоматически экранирует привязку данных// Но есть опасные паттерны:
// ❌ innerHTML без sanitization@Component({ template: `<div [innerHTML]="userInput"></div>` // Опасно если userInput не очищен!})
// ✅ Используй DomSanitizerimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({ template: `<div [innerHTML]="safeContent"></div>`})export class ContentComponent { safeContent: SafeHtml;
constructor(private sanitizer: DomSanitizer) { const trustedContent = '<b>Безопасный HTML</b>'; // Используй только для ДОВЕРЕННОГО контента (из своего CMS) this.safeContent = this.sanitizer.bypassSecurityTrustHtml(trustedContent); }}
// ✅ CSP заголовки (nginx.conf)// add_header Content-Security-Policy "default-src 'self'; script-src 'self';";
// ✅ HTTP Only cookies для JWT (не localStorage!)// localStorage → виден JS → уязвим к XSS// HTTP Only cookie → недоступен JS → безопаснее
// ✅ CSRF защита// HttpClient автоматически отправляет XSRF-TOKEN заголовок// if (cookie contains XSRF-TOKEN)Типизация: строгий TypeScript
Заголовок раздела «Типизация: строгий TypeScript»// tsconfig.json — максимальная строгость{ "compilerOptions": { "strict": true, // включает все strict флаги "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, }}
// ✅ Не используй anyfunction process(data: unknown): string { if (typeof data === 'string') return data; if (typeof data === 'number') return data.toString(); return JSON.stringify(data);}
// ✅ Type guardsfunction isUser(obj: unknown): obj is User { return typeof obj === 'object' && obj !== null && 'id' in obj;}
// ✅ Const assertionsconst ROLES = ['admin', 'user', 'moderator'] as const;type Role = typeof ROLES[number]; // 'admin' | 'user' | 'moderator'Финальный чеклист проекта ✅
Заголовок раздела «Финальный чеклист проекта ✅»Архитектура:☑ Feature modules с lazy loading☑ Core/Shared/Features структура☑ Facade pattern для стейта☑ Smart/Dumb компоненты☑ Barrel exports (index.ts)
Производительность:☑ OnPush везде☑ trackBy в ngFor☑ async pipe☑ @defer для heavy компонентов☑ NgOptimizedImage
Безопасность:☑ Нет localStorage для JWT☑ DomSanitizer для HTML контента☑ CSP заголовки☑ HTTP interceptor для auth
Качество кода:☑ strict TypeScript☑ ESLint + Prettier☑ Unit тесты (80%+ coverage)☑ E2E тесты для критических путей
DevOps:☑ Budgets в angular.json☑ CI/CD pipeline☑ Production builds с source maps (hidden)☑ nx affected для монорепоPlayground 🎮
Заголовок раздела «Playground 🎮»Визуализатор кода: антипаттерны vs лучшие практики: