Reader Stacks

Looping Over Objects (Not Just Arrays) in Angular

*ngFor only iterates arrays and other iterables natively — looping over a plain object's keys and values needs either the built-in KeyValue pipe or converting the object first.

*ngFor is built to iterate arrays (and other iterables implementing Symbol.iterator) — a plain JavaScript object, like { red: '#ff0000', green: '#00ff00' }, doesn't implement that iteration protocol, so *ngFor can't loop over it directly without some form of conversion first.

1. The problem

colors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff' };
<!-- This does NOT work — colors is a plain object, not an iterable -->
<div *ngFor="let color of colors">{{ color }}</div>

2. The built-in fix: the KeyValue pipe

<div *ngFor="let entry of colors | keyvalue">
  {{ entry.key }}: {{ entry.value }}
</div>

Angular's built-in KeyValuePipe converts a plain object into an array of {key, value} pairs at template-render time — no manual conversion code needed in the component class at all, and it works equally on a plain object or a Map.

3. Default sort order — and how to control it

By default, keyvalue sorts entries alphabetically by key — for many real objects (a settings object with a meaningful, deliberate property order) this reorders things unexpectedly compared to how the object was originally defined. Passing a custom compare function preserves — or controls — a different order:

<div *ngFor="let entry of colors | keyvalue: originalOrder">
  {{ entry.key }}: {{ entry.value }}
</div>
originalOrder = (): number => 0; // a comparator that always returns 0 disables re-sorting

4. Converting to an array in the component instead

colors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff' };

colorEntries = Object.entries(this.colors).map(([key, value]) => ({ key, value }));
<div *ngFor="let entry of colorEntries">
  {{ entry.key }}: {{ entry.value }}
</div>

Converting once in the component (rather than via a pipe evaluated on every change-detection cycle) is worth doing for a large, rarely-changing object — an impure pipe re-runs on every change detection pass by default, which for a big object can add measurable overhead compared to a conversion done once and cached as a component property.

5. Iterating a Map instead of a plain object

colorMap = new Map([
  ['red', '#ff0000'],
  ['green', '#00ff00'],
]);
<div *ngFor="let entry of colorMap | keyvalue">
  {{ entry.key }}: {{ entry.value }}
</div>

A native Map preserves insertion order reliably (unlike plain object key order, which has some JavaScript-specification quirks around numeric-looking keys) and the same keyvalue pipe works on it identically — worth considering as the data structure itself, not just a workaround, when key order genuinely matters.

6. With the newer @for control-flow syntax

@for (entry of colors | keyvalue; track entry.key) {
  <div>{{ entry.key }}: {{ entry.value }}</div>
}

The keyvalue pipe works identically with Angular's newer built-in @for syntax — only the loop syntax itself changes; the object-to-array conversion problem and its solution stay exactly the same.

Topics: Developer Productivity