لیست ها (Lists)
می خواهیم «لیست ها در انگولار» را مثل بازی فهرست اسکن ها بفهمیم. «لیست (List)» یعنی چند آیتم پشت سرهم. با حلقه رندر می گیریم. با «سیگنال (Signal)» وضعیت را نگه می داریم. با «شناسه پایدار (Stable Identity)» جابه جایی ها را بدون چشمک DOM مدیریت می کنیم. سپس با «محاسبه شده (computed)» فیلتر و مرتب سازی می سازیم.
تعریف سریع
حلقه @for آیتم ها را تکرار می کند. شاخص i را با let i = $index می گیریم. سیگنال، آرایه را نگه می دارد. با set() و update() همیشه یک آرایه تازه می سازیم. «شناسه پایدار» مثل id کمک می کند DOM دوباره کاری نکند. برای نماهای مشتق از computed() کمک بگیرید.
نکته: برای کنترل جریان، Control Flow و برای شرطی ها Conditional Rendering را هم ببینید.
لیست ها در انگولار: شروع سریع
الگوی پایه ساده است. یک سیگنال از آرایه می سازیم. سپس با @for رندر می کنیم. اگر لیست خالی شد، با @empty پیام جایگزین نشان می دهیم.
لیست پایه با @for و @empty
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Lists</h3>
<ul>
@for (item of items(); let i = $index; track item) {
<li>{{ i + 1 }}. {{ item }}</li>
} @empty {
<li>No items</li>
}
</ul>
<button (click)="add()">Add Item</button>
<button (click)="clear()">Clear</button>
<button (click)="reset()">Reset</button>
`
})
export class App {
items = signal(['Angular', 'React', 'Vue']);
add() {
this.items.update(arr => [...arr, 'Svelte']);
}
clear() {
this.items.set([]);
}
reset() {
this.items.set(['Angular', 'React', 'Vue']);
}
}
bootstrapApplication(App);
نکته: هرگز آرایه را درجا دست کاری نکن. همیشه یک کپی تازه بساز.
لیست با track و شناسه پایدار
وقتی آیتم ها جابه جا می شوند، track کمک می کند DOM الکی نسازد. از it.id یا کلید یکتا استفاده کن.
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
type Item = { id: number; name: string };
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Lists with track</h3>
<ul>
@for (it of items(); let i = $index; track it.id) {
<li>{{ i + 1 }}. {{ it.name }} (id: {{ it.id }})</li>
}
</ul>
<button (click)="renameFirst()">Rename first</button>
<button (click)="shuffle()">Shuffle</button>
<button (click)="add()">Add item</button>
`
})
export class App {
items = signal<Item[]>([
{ id: 1, name: 'Angular' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Vue' }
]);
nextId = 4;
renameFirst() {
this.items.update(arr => arr.map((it, i) => {
if (i === 0) {
return { ...it, name: it.name + ' *' };
}
return it;
}));
}
shuffle() {
this.items.update(arr => {
const copy = [...arr];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
});
}
add() {
this.items.update(arr => {
return [...arr, { id: this.nextId++, name: 'New ' + Date.now() }];
});
}
}
bootstrapApplication(App);
هشدار: از شاخص آرایه به عنوان شناسه استفاده نکن. با جابه جایی، ناسازگاری می گیری.
فیلتر و مرتب سازی با computed()
یک نما از داده ها بساز. منبع را دست نزن. سپس بر اساس جستجو و کلید مرتب سازی خروجی بده.
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
type Product = { name: string; price: number };
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<h3>Filter & Sort</h3>
<div style="display:flex;gap:8px;margin-bottom:8px;">
<label>Search: <input #q (input)="query.set(q.value)" placeholder="Type to filter..." /></label>
<button (click)="setSort('name')">Sort by Name</button>
<button (click)="setSort('price')">Sort by Price</button>
<button (click)="toggleDir()">{{ sortDir() === 1 ? 'Asc' : 'Desc' }}</button>
</div>
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr><th style="border:1px solid #ddd;padding:8px;background:#f7f7f7;">Name</th><th style="border:1px solid #ddd;padding:8px;background:#f7f7f7;width:140px;">Price</th></tr>
<\/thead>
<tbody>
@for (p of view(); track p.name) {
<tr>
<td style="border:1px solid #ddd;padding:8px;">{{ p.name }}</td>
<td style="border:1px solid #ddd;padding:8px;">{{ p.price | currency:'USD' }}</td>
</tr>
}
<\/tbody>
<\/table>
`
})
export class App {
items = signal<Product[]>([
{ name: 'Angular', price: 0 },
{ name: 'React', price: 0 },
{ name: 'Vue', price: 0 },
{ name: 'Svelte', price: 0 },
{ name: 'Solid', price: 0 },
{ name: 'Lit', price: 0 }
]);
query = signal('');
sortKey = signal<'name' | 'price'>('name');
sortDir = signal<1 | -1>(1);
view = computed(() => {
const q = this.query().toLowerCase();
const dir = this.sortDir();
const key = this.sortKey();
return this.items()
.filter(it => it.name.toLowerCase().includes(q))
.sort((a, b) => {
const av: any = (a as any)[key];
const bv: any = (b as any)[key];
if (av < bv) {
return -1 * dir;
}
if (av > bv) {
return 1 * dir;
}
return 0;
});
});
setSort(key: 'name' | 'price') {
if (this.sortKey() === key) {
this.toggleDir();
return;
}
this.sortKey.set(key);
}
toggleDir() {
this.sortDir.set(this.sortDir() === 1 ? -1 : 1);
}
}
bootstrapApplication(App);
نکته: کار سنگین را در قالب انجام نده. با computed از قبل آماده کن.
جمع بندی سریع
- @for برای تکرار و @empty برای حالت خالی.
- سیگنال ها را فقط با set یا update تغییر بده.
- برای جابه جایی ها، از track با کلید یکتا استفاده کن.
- نمای فیلتر و سورت را با computed بساز.
- هرگز شاخص آرایه را کلید نکن.