JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Структурные директивы: *ngFo...

Структурные директивы: *ngFor, сравнение с map в React

Модуль 4: Node.js, Next.js и Angular
14 уровень , 2 лекция
Открыта

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 позволяет получать дополнительные переменные:

  • firsttrue для первого элемента
  • lasttrue для последнего
  • eventrue для чётных индексов
  • oddtrue для нечётных

Пример с выделением первого и последнего пользователя:

<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())
В шаблоне:
*ngFor="let x of xs"
В JSX:
xs.map((x) => (...))
Ключ через 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 может получиться некорректным.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ