1. *ngFor: перебор коллекций и рендеринг списков
Зачем нужен *ngFor?
В реальных приложениях практически всегда приходится работать со списками данных: массив товаров, пользователи в чате, сообщения, статьи, комментарии... Рисовать их вручную — занятие для людей с очень много свободного времени (или для тех, кто ещё не знает про *ngFor!).
Angular предлагает лаконичный и очень мощный способ — директива *ngFor. Она работает примерно как привычный вам цикл for в JavaScript, только прямо в HTML-шаблоне.
Базовый синтаксис *ngFor
<li *ngFor="let item of items">{{ item }}</li>
Пояснение:
- let item of items — объявляем переменную item, которая по очереди принимает значения из массива items.
- Внутри тега мы можем использовать переменную item, чтобы вывести её содержимое или использовать в других привязках.
Пример: список пользователей
Допустим, у нас есть компонент с массивом пользователей:
// user-list.component.ts
export class UserListComponent {
users = ['Алиса', 'Боб', 'Чарли', 'Дейв'];
}
И шаблон:
<!-- user-list.component.html -->
<ul>
<li *ngFor="let user of users">{{ user }}</li>
</ul>
Что произойдёт?
Angular создаст для каждого пользователя свой <li>, и выведет имена по порядку. Если в массиве 100 пользователей — будет 100 <li>. Если массив пуст — не будет ни одного элемента.
2. Разбор синтаксиса: let, of, index и другие переменные
Основной синтаксис
*ngFor="let element of collection"
Пояснение:
- let element — объявление переменной (аналогично for (let element of collection) в JS).
- of collection — указываем, по какому массиву перебираем.
Индекс элемента
Иногда нужно знать не только сам элемент, но и его порядковый номер (например, чтобы вывести нумерованный список или для ключа).
<li *ngFor="let user of users; let i = index">
{{ i + 1 }}. {{ user }}
</li>
Пояснение:
let i = index — создаёт переменную i, в которую Angular положит текущий индекс элемента (от 0).
Последний элемент, первый элемент, чётность/нечётность
Angular позволяет получать дополнительные переменные:
- first — true для первого элемента
- last — true для последнего
- even — true для чётных индексов
- odd — true для нечётных
Пример с выделением первого и последнего пользователя:
<li *ngFor="let user of users; let first = first; let last = last">
<span *ngIf="first">👑</span>
{{ user }}
<span *ngIf="last">🏁</span>
</li>
Пример с чётностью
<li *ngFor="let user of users; let odd = odd">
<span [style.color]="odd ? 'red' : 'black'">{{ user }}</span>
</li>
3. Практика: выводим список задач
Давайте продолжим развивать наше учебное приложение. Пусть у нас есть компонент задач:
// tasks.component.ts
export class TasksComponent {
tasks = [
{ title: 'Сделать домашку', done: false },
{ title: 'Погулять с собакой', done: true },
{ title: 'Купить хлеб', done: false }
];
}
Шаблон для отображения списка:
<!-- tasks.component.html -->
<ul>
<li *ngFor="let task of tasks; let i = index">
<input type="checkbox" [checked]="task.done" />
{{ i + 1 }}. {{ task.title }}
</li>
</ul>
Что происходит?
Для каждого объекта в массиве tasks Angular создаёт <li>.
В каждом элементе отображаем чекбокс (можно сделать его интерактивным позже) и текст задачи.
4. Вложенные *ngFor: списки внутри списков
Иногда приходится выводить "список списков". Например, у нас есть массив пользователей, и у каждого — свой массив задач.
// users-tasks.component.ts
export class UsersTasksComponent {
users = [
{
name: 'Алиса',
tasks: ['Почистить зубы', 'Сделать зарядку']
},
{
name: 'Боб',
tasks: ['Проверить почту', 'Вынести мусор']
}
];
}
Шаблон с вложенным *ngFor:
<!-- users-tasks.component.html -->
<div *ngFor="let user of users">
<h3>{{ user.name }}</h3>
<ul>
<li *ngFor="let task of user.tasks">{{ task }}</li>
</ul>
</div>
Angular не боится вложенных циклов — главное, чтобы вы не запутались сами!
5. Ключи в *ngFor: trackBy для оптимизации
Если ваш список часто изменяется (например, добавляются и удаляются элементы), Angular по умолчанию сравнивает элементы по их положению в массиве. Это может приводить к лишнему перерисовыванию (например, если вы вставили элемент в начало — все остальные "сдвинутся").
Чтобы этого избежать, используйте функцию trackBy, которая подскажет Angular, как искать "тот же" элемент по уникальному идентификатору (например, по id):
// tasks.component.ts
export class TasksComponent {
tasks = [
{ id: 1, title: 'Сделать домашку', done: false },
{ id: 2, title: 'Погулять с собакой', done: true }
];
trackByTaskId(index: number, task: any): number {
return task.id;
}
}
<li *ngFor="let task of tasks; trackBy: trackByTaskId">
{{ task.title }}
</li>
Зачем это нужно?
Если вы обновляете массив задач, Angular будет сравнивать элементы по id, а не просто по индексу, и не будет лишний раз удалять и создавать DOM-элементы. Это особенно важно для производительности в больших списках.
6. Сравнение *ngFor с map в React
Если вы уже знакомы с React, то знаете, что для вывода списка элементов используется метод массива .map():
// React
<ul>
{items.map((item, i) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
Похожее, но не одинаковое:
- В React мы пишем JavaScript прямо в JSX, вызывая .map() по массиву.
- В Angular мы используем декларативную директиву *ngFor в шаблоне, а не пишем JS-код.
- В React обязателен атрибут key для оптимизации рендера. В Angular — опциональная функция trackBy.
- В Angular нет необходимости вручную возвращать элементы из функции — всё делается декларативно.
| Angular (*ngFor) | React (.map()) |
|---|---|
В шаблоне: |
В JSX: |
| Ключ через trackBy (опционально) | Атрибут key (обязательно) |
| Нет явного return | Явный return из функции |
| Можно получать index, first, last, even, odd | index: второй аргумент функции |
| Директива управляет DOM | Вы возвращаете элементы вручную |
Пример:
Angular:
<li *ngFor="let user of users; let i = index">{{ i + 1 }}. {{ user }}</li>
React:
{users.map((user, i) => <li key={i}>{i + 1}. {user}</li>)}
7. Типичные ошибки при использовании *ngFor
Ошибка №1: Неправильный синтаксис (забыли let)
Часто пишут *ngFor="user of users" вместо *ngFor="let user of users". Без let Angular не поймёт, что вы хотите.
Ошибка №2: Перебор не массива
Если переменная users не массив (например, undefined или объект), *ngFor просто ничего не выведет. Проверьте, что вы действительно передаёте массив.
Ошибка №3: Отсутствие trackBy при сложных изменениях
Если вы часто сортируете, добавляете или удаляете элементы в большом списке, не указывая trackBy, Angular может перерисовывать весь список, что негативно скажется на производительности.
Ошибка №4: Использование index как уникального идентификатора
В простых случаях это нормально, но если элементы могут удаляться или меняться местами, индексы "поплывут", и Angular может неправильно повторно использовать DOM-элементы. Лучше использовать уникальное поле (id).
Ошибка №5: Вложенные *ngFor без явного контейнера
Если вы пишете несколько вложенных *ngFor подряд без оборачивающего элемента (например, без <div> или <ng-container>), HTML может получиться некорректным.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ