SOLID va Clean Code
SOLID tamoyillari va Angular Style Guide — katta jamoada kod sifati, kengaytirish va test qilish uchun asos. Bu guruh intervyuda arxitektura va clean code savollarini qamrab oladi.
Single Responsibility — har class bitta mas'uliyat
Nima bu?
Single Responsibility Principle (SRP) — har bir class/modul faqat bitta o'zgarish sababi bo'lishi kerak. Angular'da: komponent faqat UI, service — biznes logika, guard — route himoya, interceptor — HTTP middleware. "God component" — ham HTTP, ham validatsiya, ham formatlash bir joyda — SRP buziladi.
Kod misoli
// ❌ SRP buzilgan — hamma narsa bir komponentda
@Component({ /* ... */ })
export class BadUserPageComponent {
users: User[] = [];
ngOnInit() {
this.http.get<User[]>('/api/users').subscribe((data) => {
this.users = data.map((u) => ({
...u,
displayName: `${u.firstName} ${u.lastName}`.trim(),
isActive: u.lastLogin > Date.now() - 30 * 86400000,
}));
});
}
}
// ✅ Mas'uliyatlar ajratilgan
import { Injectable, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
}
@Injectable({ providedIn: 'root' })
export class UserMapper {
toViewModel(user: User): UserViewModel {
return {
...user,
displayName: `${user.firstName} ${user.lastName}`.trim(),
isActive: user.lastLogin > Date.now() - 30 * 86400000,
};
}
}
@Component({
selector: 'app-user-page',
standalone: true,
template: `
@for (u of users$ | async; track u.id) {
<p>{{ u.displayName }} — {{ u.isActive ? 'Faol' : 'Nofaol' }}</p>
}
`,
imports: [AsyncPipe],
})
export class UserPageComponent {
private userService = inject(UserService);
private mapper = inject(UserMapper);
users$ = this.userService.getUsers().pipe(
map((users) => users.map((u) => this.mapper.toViewModel(u)))
);
}
Imtihonda
- SRP buzilgan komponentni qanday refactor qilasiz?
- Service va util/helper class farqi?
- "Bitta mas'uliyat" deganda Angular kontekstida nima tushuniladi?
Yodlash uchun
Bitta class = bitta sabab o'zgarishi; UI ≠ biznes logika.
Open/Closed — kengaytirish uchun ochiq
Nima bu?
Open/Closed Principle (OCP) — class kengaytirish uchun ochiq, o'zgartirish uchun yopiq. Yangi funksiya qo'shish uchun mavjud kodni o'zgartirmasdan yangi implementatsiya qo'shiladi. Angular'da: abstract class/interface, Strategy pattern, custom validator/pipe/directive ro'yxatga qo'shish, InjectionToken bilan plugin arxitektura.
Kod misoli
// Abstract strategy — yangi to'lov turi qo'shish oson
export abstract class PaymentStrategy {
abstract pay(amount: number): Observable<PaymentResult>;
}
@Injectable()
export class CardPaymentStrategy extends PaymentStrategy {
private http = inject(HttpClient);
pay(amount: number): Observable<PaymentResult> {
return this.http.post<PaymentResult>('/api/pay/card', { amount });
}
}
@Injectable()
export class PaymePaymentStrategy extends PaymentStrategy {
private http = inject(HttpClient);
pay(amount: number): Observable<PaymentResult> {
return this.http.post<PaymentResult>('/api/pay/payme', { amount });
}
}
export const PAYMENT_STRATEGY = new InjectionToken<PaymentStrategy>('PaymentStrategy');
@Component({
selector: 'app-checkout',
standalone: true,
providers: [
{ provide: PAYMENT_STRATEGY, useClass: CardPaymentStrategy },
],
template: `<button type="button" (click)="checkout()">To'lash</button>`,
})
export class CheckoutComponent {
private payment = inject(PAYMENT_STRATEGY);
checkout(): void {
this.payment.pay(99_000).subscribe(/* ... */);
}
}
// Route config kengaytirish — mavjud guard o'zgartirilmaydi
export const authGuard: CanActivateFn = () => inject(AuthService).isLoggedIn();
export const adminGuard: CanActivateFn = () => inject(AuthService).isAdmin();
// Yangi route — faqat config qo'shiladi
{ path: 'admin', canActivate: [authGuard, adminGuard], loadComponent: () => import('./admin') }
Imtihonda
- OCP va Dependency Injection bog'liqligi?
if/elsezanjiri o'rniga qanday pattern ishlatiladi?- Angular'da OCP buzilgan misol?
Yodlash uchun
Yangi funksiya = yangi class; eski kodni tegmang.
Dependency Inversion — abstrakt'ga bog'liq bo'l
Nima bu?
Dependency Inversion Principle (DIP) — yuqori darajadagi modullar past darajadagi modullarga bog'liq bo'lmasligi kerak; ikkalasi ham abstraksiyaga bog'lanadi. Angular DI buni tabiiy qo'llab-quvvatlaydi: interface + InjectionToken, @Injectable implementatsiya almashtirish, test'da mock inject qilish.
Kod misoli
// Abstraksiya — konkrét API'dan mustaqil
export interface Logger {
log(message: string, context?: string): void;
error(message: string, error?: unknown): void;
}
export const LOGGER = new InjectionToken<Logger>('Logger');
@Injectable()
export class ConsoleLogger implements Logger {
log(message: string, context?: string): void {
console.log(`[${context ?? 'App'}]`, message);
}
error(message: string, error?: unknown): void {
console.error(message, error);
}
}
@Injectable()
export class RemoteLogger implements Logger {
private http = inject(HttpClient);
log(message: string, context?: string): void {
this.http.post('/api/logs', { level: 'info', message, context }).subscribe();
}
error(message: string, error?: unknown): void {
this.http.post('/api/logs', { level: 'error', message, error: String(error) }).subscribe();
}
}
// Yuqori daraja — faqat Logger interface'ga bog'liq
@Injectable({ providedIn: 'root' })
export class OrderService {
private logger = inject(LOGGER);
submit(order: Order): Observable<void> {
this.logger.log(`Buyurtma #${order.id} yuborildi`, 'OrderService');
return this.http.post<void>('/api/orders', order);
}
}
// app.config.ts — implementatsiya almashtirish
providers: [
{ provide: LOGGER, useClass: environment.production ? RemoteLogger : ConsoleLogger },
]
Imtihonda
- DIP va Angular DI farqi/nisbati?
InjectionTokenqachon kerak (interface runtime'da yo'q)?- Test'da DIP qanday yordam beradi?
Yodlash uchun
Interface/Token → inject; concrete class'ga to'g'ridan-to'g'ri bog'lanmang.
Naming convention — Angular style guide
Nima bu?
Angular Style Guide nomlash qoidalarini belgilaydi: fayl nomi kebab-case + suffix (user-profile.component.ts); class PascalCase + suffix (UserProfileComponent); selector kebab-case, prefiks bilan (app-user-profile); service — @Injectable, -service suffix; constant — UPPER_SNAKE. Bir feature = bir papka (component + html + scss + spec).
Kod misoli
src/app/features/users/
├── user-list/
│ ├── user-list.component.ts → UserListComponent
│ ├── user-list.component.html
│ ├── user-list.component.scss
│ └── user-list.component.spec.ts
├── user-detail/
│ └── ...
├── services/
│ └── user.service.ts → UserService
├── models/
│ └── user.model.ts → interface User
└── users.routes.ts → usersRoutes
// Selector prefiks — loyiha prefiksi (app, lib, admin)
@Component({
selector: 'app-user-list', // element
// selector: 'app-user-list', // attribute: <div app-user-list>
templateUrl: './user-list.component.html',
})
export class UserListComponent {}
// Output naming — verb + Change (two-way uchun)
@Output() selectedUserChange = new EventEmitter<User>();
// Observable suffix $
users$ = this.userService.getUsers();
// Boolean — is/has/can prefiks
isLoading = false;
hasPermission = true;
Imtihonda
- Nima uchun
Componentsuffix majburiy deyiladi? - Selector prefiks nima uchun kerak?
$suffix Observable uchun convention qayerdan?
Yodlash uchun
kebab-case file; PascalCase class + suffix; selector prefiks.
Barrel exports — index.ts pattern
Nima bu?
Barrel file (index.ts) — papkadagi public API'ni bitta joydan export qiladi. Import qisqaradi: from './users' o'rniga from './users/user-list.component'. Ehtiyot: circular dependency va tree shaking muammolari — faqat public surface export qiling, ichki helper'larni export qilmang.
Kod misoli
// features/users/index.ts — public API
export { UserListComponent } from './user-list/user-list.component';
export { UserDetailComponent } from './user-detail/user-detail.component';
export { UserService } from './services/user.service';
export type { User, UserViewModel } from './models/user.model';
// Ichki helper export QILINMAYDI — _user.mapper.ts
// Boshqa modul — qisqa import
import { UserListComponent, UserService, User } from '@app/features/users';
@Component({
standalone: true,
imports: [UserListComponent],
})
export class DashboardComponent {
private users = inject(UserService);
}
// tsconfig paths — alias bilan
// "paths": { "@app/features/users": ["src/app/features/users/index.ts"] }
// ❌ Circular dependency xavfi
// index.ts → component → index.ts (orqaga import)
// ✅ To'g'ri — component ichki fayllardan to'g'ridan-to'g'ri import
import { UserMapper } from '../services/user.mapper';
Imtihonda
- Barrel export afzallik va kamchiliklari?
- Circular dependency qanday aniqlanadi va hal qilinadi?
- Library (
ng build) uchun public-api.ts farqi?
Yodlash uchun
index.ts = public API; ichki modul export qilmang; circular'dan saqlaning.
