*ngFor для объектов: как получить ключи и значения без потери производительности

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Проблема, которая знакома каждому Angular-разработчику

Вы когда-нибудь пытались вывести в шаблоне Angular содержимое обычного JavaScript-объекта с помощью *ngFor и вдруг понимали: "А как мне достать и ключ, и значение?" Ведь *ngFor по умолчанию работает только с массивами. А объект — это не массив.

Этот вопрос — один из самых популярных на Stack Overflow (тысячи просмотров и десятки ответов). Разработчики ищут не просто решение, а правильное решение — то, которое не убьёт производительность приложения, особенно когда объект большой или часто обновляется.

В этой статье мы не просто дадим ответ. Мы разберём все основные подходы, от простых до продвинутых, и покажем, как писать код, который будет работать быстро и стабильно. Поехали!

Почему *ngFor не работает с объектами "из коробки"?

Директива *ngFor в Angular предназначена для итерируемых структур (Iterable). Массив — это итерируемая структура. Объект — нет. Когда вы пишете:

<div *ngFor="let item of myObject">{{ item }}</div>

Angular выдаст ошибку: "Cannot find a differ supporting object '...'". Angular просто не знает, как перебрать объект.

Но на практике нам часто нужно вывести данные из объекта: конфиги, словари, ответы API, где ключи имеют смысл (например, { name: 'Анна', age: 25, city: 'Москва' }).

Самый простой способ: Object.keys() и Object.entries()

Самый популярный и очевидный ответ на Stack Overflow — преобразовать объект в массив прямо в шаблоне или в компоненте.

Вариант 1: Object.keys() — получаем только ключи

// В компоненте (TypeScript)
export class MyComponent {
  myObject = { name: 'Анна', age: 25, city: 'Москва' };
}
<!-- В шаблоне -->
<div *ngFor="let key of objectKeys(myObject)">
  Ключ: {{ key }}, Значение: {{ myObject[key] }}
</div>

Но тут есть нюанс: objectKeys должна быть доступна в шаблоне. Либо делаем её методом компонента, либо выносим в отдельное свойство:

// Компонент
export class MyComponent {
  myObject = { name: 'Анна', age: 25, city: 'Москва' };
  objectKeys = Object.keys; // просто ссылка на нативную функцию
}

Это работает, но вызывает функцию при каждой проверке изменений (change detection). Если объект большой — это удар по производительности.

Вариант 2: Object.entries() — ключи и значения сразу

// Компонент
export class MyComponent {
  myObject = { name: 'Анна', age: 25, city: 'Москва' };
  get entries() {
    return Object.entries(this.myObject);
  }
}
<!-- Шаблон -->
<div *ngFor="let entry of entries">
  Ключ: {{ entry[0] }}, Значение: {{ entry[1] }}
</div>

Или с деструктуризацией (более читаемо):

<div *ngFor="let [key, value] of entries">
  Ключ: {{ key }}, Значение: {{ value }}
</div>

Плюсы: просто, понятно, наглядно.
Минусы: метод entries будет вызываться при каждом цикле обнаружения изменений. Для статичных объектов — ок, для динамических — может стать проблемой.

Производительность: почему это важно?

Представьте, что ваш объект содержит 10 000 записей. Или он обновляется каждые 100 мс (например, данные с сервера в реальном времени). Каждый вызов Object.entries() создаёт новый массив. Angular при каждом цикле обнаружения изменений увидит "новый" массив и перерисует весь список. Это приводит к дёрганью интерфейса, потере фокуса на инпутах и тормозам.

Поэтому для серьёзных проектов нужно использовать оптимизированные подходы.

Правильный путь: KeyValuePipe (но с оговорками)

Angular предоставляет встроенный пайп KeyValuePipe. Он преобразует объект в массив пар { key, value } прямо в шаблоне:

<div *ngFor="let item of myObject | keyvalue">
  Ключ: {{ item.key }}, Значение: {{ item.value }}
</div>

Это удобно, лаконично и работает "из коробки". Но!

Важное предупреждение: KeyValuePipe в некоторых версиях Angular (особенно старых) может считаться impure (нечистым). Это значит, что он будет вызываться каждый раз при любом изменении в приложении. Для больших объектов это всё ещё проблема.

В Angular 17+ поведение улучшили, но всё равно стоит быть внимательным. Если объект статичен или меняется редко — KeyValuePipe — ваш выбор. Если объект гигантский — читайте дальше.

Оптимизация №1: trackBy — спасатель производительности

Независимо от того, какой способ вы выбрали, всегда используйте trackBy в *ngFor. Это директива, которая помогает Angular понимать, какие элементы изменились, а какие нет. Без неё Angular удаляет все DOM-элементы и создаёт их заново при любом изменении массива.

// В компоненте
trackByKey(index: number, item: any): string {
  return item.key; // уникальный ключ
}
<!-- В шаблоне -->
<div *ngFor="let item of myObject | keyvalue; trackBy: trackByKey">
  {{ item.key }}: {{ item.value }}
</div>

Это резко снижает количество перерисовок. Angular будет переиспользовать существующие DOM-элементы, если ключ не изменился.

Оптимизация №2: Мемоизация (кэширование) с помощью pure-пайпа

Если вы не хотите использовать KeyValuePipe (или он вам не подходит), создайте собственный pure-пайп. Pure-пайп вызывается только тогда, когда меняется входное значение (по ссылке), а не при каждом цикле обнаружения изменений.

// key-value-pure.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'keyValuePure',
  pure: true // по умолчанию true, но явно указываем
})
export class KeyValuePurePipe implements PipeTransform {
  transform(obj: any): { key: string; value: any }[] {
    if (!obj) return [];
    return Object.keys(obj).map(key => ({ key,                

Блоги

Рекомендаций по книгам