Komponent arxitekturasi
Katta Angular ilovalarida komponentlarni Smart (Container) va Dumb (Presentation) ga ajratish, @Input/@Output orqali bir yo'nalishli data flow va service/state orqali aloqa — maintainability va test qilishni osonlashtiradi.
Smart (Container) vs Dumb (Presentation) component
Nima bu?
Smart (Container) komponent — biznes logika, HTTP, store bilan ishlaydi; state boshqaradi va Dumb komponentlarga data uzatadi. Dumb (Presentation) komponent — faqat @Input orqali data oladi, @Output orqali event yuboradi; service inject qilmaydi, qayta ishlatiladigan UI bo'ladi. Bu pattern test va refactor'ni soddalashtiradi.
Kod misoli
// Dumb — faqat ko'rsatish
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
export interface Product {
id: number;
name: string;
price: number;
}
@Component({
selector: 'app-product-card',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<article class="card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency:'UZS':'symbol':'1.0-0' }}</p>
<button type="button" (click)="addToCart.emit(product.id)">
Savatga
</button>
</article>
`,
imports: [CurrencyPipe],
})
export class ProductCardComponent {
@Input({ required: true }) product!: Product;
@Output() addToCart = new EventEmitter<number>();
}
// Smart — data va logika
import { Component, inject, Output, EventEmitter } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { ProductCardComponent, Product } from './product-card.component';
import { ProductService } from './product.service';
import { CartService } from './cart.service';
@Component({
selector: 'app-product-list-page',
standalone: true,
imports: [ProductCardComponent, AsyncPipe],
template: `
@if (products$ | async; as products) {
<div class="grid">
@for (p of products; track p.id) {
<app-product-card
[product]="p"
(addToCart)="onAddToCart($event)"
/>
}
</div>
}
`,
})
export class ProductListPageComponent {
private productService = inject(ProductService);
private cartService = inject(CartService);
products$ = this.productService.getProducts();
onAddToCart(productId: number): void {
this.cartService.add(productId);
}
}
Imtihonda
- Smart komponent qaysi mas'uliyatlarni oladi?
- Dumb komponentda service inject qilish nima uchun tavsiya etilmaydi?
- Bu pattern OnPush bilan qanday uyg'unlashadi?
Yodlash uchun
Smart = state + logika; Dumb = Input/Output, UI only.
@Input/@Output convention — unidirectional flow
Nima bu?
Unidirectional data flow — data yuqoridan pastga @Input orqali oqadi, o'zgarishlar pastdan yuqoriga @Output event orqali xabar qilinadi. Parent @Input ni o'zgartiradi; child @Input ni mutate qilmasligi kerak. Angular 17.1+ da input() va output() signal-based API ham mavjud. Naming: Input — noun (user, items), Output — verb (save, delete, selectedChange).
Kod misoli
import { Component, inject, input, output, computed } from '@angular/core';
interface UserForm {
name: string;
email: string;
}
@Component({
selector: 'app-user-form',
standalone: true,
template: `
<form (ngSubmit)="submit()">
<input [value]="value().name"
(input)="patch({ name: $any($event.target).value })" />
<input [value]="value().email"
(input)="patch({ email: $any($event.target).value })" />
<button type="submit" [disabled]="!isValid()">Saqlash</button>
<button type="button" (click)="cancelled.emit()">Bekor</button>
</form>
`,
})
export class UserFormComponent {
// Signal-based Input/Output (Angular 17.1+)
value = input.required<UserForm>();
save = output<UserForm>();
cancelled = output<void>();
isValid = computed(() =>
this.value().name.trim().length > 0 &&
this.value().email.includes('@')
);
private patch(partial: Partial<UserForm>): void {
// Child Input'ni mutate qilmaydi — event yuboradi
this.save.emit({ ...this.value(), ...partial });
}
submit(): void {
if (this.isValid()) {
this.save.emit(this.value());
}
}
}
// Parent — state egasi
@Component({
selector: 'app-user-page',
standalone: true,
imports: [UserFormComponent],
template: `
<app-user-form
[value]="formState"
(save)="onSave($event)"
(cancelled)="onCancel()"
/>
`,
})
export class UserPageComponent {
formState: UserForm = { name: '', email: '' };
onSave(data: UserForm): void {
this.formState = { ...data }; // immutable update
console.log('Saqlash:', data);
}
onCancel(): void {
this.formState = { name: '', email: '' };
}
}
Imtihonda
- Child
@Inputob'ektini mutate qilsa nima bo'ladi (OnPush bilan)? selectedChangenaming convention qayerdan keladi?input()/output()va@Input/@Outputfarqi?
Yodlash uchun
Data pastga (Input), event yuqoriga (Output); child state mutate qilmasin.
Component communication — service, state
Nima bu?
Sibling yoki uzoq parent-child zanjirida @Input/@Output yetarli emas — shared service yoki state store ishlatiladi. BehaviorSubject, signal() store, yoki @Injectable({ providedIn: 'root' }) event bus. Component communication usullari: Input/Output (direct), @ViewChild (parent → child), service (any), RxJS Subject, NgRx/Signals store (katta ilova).
Kod misoli
// Shared notification service
import { Injectable, signal, computed } from '@angular/core';
export interface Toast {
id: number;
message: string;
type: 'success' | 'error';
}
@Injectable({ providedIn: 'root' })
export class ToastService {
private toasts = signal<Toast[]>([]);
readonly items = computed(() => this.toasts());
show(message: string, type: Toast['type'] = 'success'): void {
const id = Date.now();
this.toasts.update((list) => [...list, { id, message, type }]);
setTimeout(() => this.dismiss(id), 4000);
}
dismiss(id: number): void {
this.toasts.update((list) => list.filter((t) => t.id !== id));
}
}
// Producer — Smart komponent
@Component({
selector: 'app-checkout',
standalone: true,
template: `<button type="button" (click)="pay()">To'lash</button>`,
})
export class CheckoutComponent {
private toast = inject(ToastService);
private orderService = inject(OrderService);
pay(): void {
this.orderService.submit().subscribe({
next: () => this.toast.show('Buyurtma qabul qilindi'),
error: () => this.toast.show('Xato yuz berdi', 'error'),
});
}
}
// Consumer — Dumb komponent (global toast)
@Component({
selector: 'app-toast-container',
standalone: true,
template: `
<div class="toast-stack">
@for (t of toastService.items(); track t.id) {
<div [class]="'toast toast-' + t.type">{{ t.message }}</div>
}
</div>
`,
})
export class ToastContainerComponent {
toastService = inject(ToastService);
}
Imtihonda
- Qachon service, qachon Input/Output tanlanadi?
providedIn: 'root'va component-level provider farqi?- EventEmitter va Subject farqi (Output vs service)?
Yodlash uchun
Sibling/uzoq aloqa → service; yaqin parent-child → Input/Output.
