skip to content

In Angular, in what order does KeyValuePipe return an object's entries, and how do you keep the original order instead?

level: middleimportance: should knowfreq 36%

answer

  1. not insertion order by default
  2. a built-in key comparator
  3. second argument is a compare function
  4. null disables sorting (v19)

basics

~10 s

KeyValuePipe sorts entries by key by default, strings by code unit and numbers numerically. Pass a compare function as its argument to change that, or null (since v19) to keep the input's natural order.

solid answer

~30 s

`KeyValuePipe` turns an object or `Map` into `{ key, value }` pairs **sorted by key** with a built-in comparator: strings compared by code unit, numbers numerically, `null`/`undefined` keys last. Plain objects have their keys read with `Object.keys()`, so numeric-looking keys become strings and `'10'` sorts before `'2'`. The pipe's second argument is a compare function; `keyvalue: null` (since v19) skips sorting and keeps the input's natural order, insertion order for a `Map`. Define custom comparators as a stable component field. The pipe is impure, but it keeps a differ and only rebuilds the array when entries or the comparator change.

code

ts · 16 lines
ts
import { Component, signal } from '@angular/core';
import { CurrencyPipe, KeyValue, KeyValuePipe } from '@angular/common';

@Component({
  selector: 'app-tax-summary',
  imports: [KeyValuePipe, CurrencyPipe],
  template: `
    @for (band of totals() | keyvalue: byAmountDesc; track band.key) {
      <p>{{ band.key }}: {{ band.value | currency: 'EUR' }}</p>
    }
  `,
})
export class TaxSummary {
  totals = signal<Record<string, number>>({ standard: 120, reduced: 45, zero: 0 });
  byAmountDesc = (a: KeyValue<string, number>, b: KeyValue<string, number>) => b.value - a.value;
}

go deeper

for a junior

Recall that keyvalue turns an object into key-value pairs and sorts them by key unless told otherwise.

for a middle

Explain the default comparator, the compare-function argument, the null option from v19 and why object keys are strings.

for a senior

Choose between a comparator, null ordering or modelling the data as an array when order is a business rule, and keep comparators stable.

for a principal

Push ordering requirements into the data contract so every view and export shows entries in the same, intended order.

## What `KeyValuePipe` does `KeyValuePipe` (template name `keyvalue`, from `@angular/common`) turns a plain object or a `Map` into an **array of `{ key, value }` pairs** so a template can iterate over it: ```html @for (entry of totalsByTax | keyvalue; track entry.key) { <dt>{{ entry.key }}</dt> <dd>{{ entry.value | currency }}</dd> } ``` The part interviewers ask about is the **order** of that array. ## The default: sorted by key By default the output is **sorted by key**, not left in insertion order. The built-in comparator: - compares two **string** keys lexicographically by UTF-16 code unit, so uppercase letters sort before lowercase and `'10'` sorts before `'9'`; - compares two **number** keys numerically (possible with a `Map`); - sorts `false` before `true` for boolean keys; - puts `null` and `undefined` keys **last**; - falls back to comparing `String(key)` when the two keys have different types. One subtlety: for a plain object the keys are read with `Object.keys()`, so even a `Record<number, V>` produces **string** keys, and `'10'` lands before `'2'`. A `Map` keeps real number keys and sorts them numerically. ## Keeping the original order The pipe takes an optional second argument, a **compare function** `(a: KeyValue<K, V>, b: KeyValue<K, V>) => number`: | Argument | Resulting order | |---|---| | omitted | the default comparator: sorted by key | | a function | whatever the function returns | | `null` | no sorting: the natural order of the input | Passing `null` became possible in Angular 19. For a `Map` the natural order is insertion order; for an object it is JavaScript's own property order, in which integer-like keys come first in ascending order, followed by the remaining string keys in insertion order. ```html @for (entry of totalsByTax | keyvalue: null; track entry.key) { ... } ``` Before v19 the usual workaround was a comparator that always returns `0`, which leaves the relative order untouched. ## Sorting by value instead A comparator can sort on anything, including the value. Define it once as a component field so the pipe receives the same function reference every time: ```ts byAmountDesc = (a: KeyValue<string, number>, b: KeyValue<string, number>) => b.value - a.value; ``` ```html @for (entry of totalsByTax | keyvalue: byAmountDesc; track entry.key) { ... } ``` Keeping it as a field makes it reusable and testable, and guarantees a stable reference; a comparator produced anew on every change detection pass, for example by a method that returns a fresh function, would force a re-sort each time. ## Why it re-runs on every check `KeyValuePipe` is declared **impure** (`pure: false`), so Angular calls it on every change detection pass. That is what lets it notice keys added to the same object. It stays cheap because it keeps a key-value differ: it rebuilds and re-sorts the array only when the differ reports a change or the compare function reference changes, and otherwise returns the same array. ## Signals and other gotchas 1. Pass the **value**, not the signal: `totals() | keyvalue`. Handing it the signal itself makes development builds warn that `KeyValuePipe` does not unwrap signals. 2. `null` or `undefined` input returns `null`, which `@for` treats as an empty list. 3. Typed keys matter: with a `Map<number, V>` numeric sorting is correct; the same data as an object sorts as strings. ## Comparing the options side by side | Goal | Write | |---|---| | Alphabetical by key | `obj \| keyvalue` | | Keep the input's own order | `obj \| keyvalue: null` | | Largest value first | `obj \| keyvalue: byAmountDesc` | | A fixed business order | iterate an array instead | ## When to avoid the pipe If the order is part of the domain, such as tax bands in a fixed legal order, store the data as an **array** in the order you want and iterate it directly. `KeyValuePipe` is best for displaying dictionaries where alphabetical order is acceptable or where a comparator expresses the rule clearly.

  • Why does KeyValuePipe on { 2: 'b', 10: 'a' } list key '10' before '2'?
    For a plain object the keys come from `Object.keys()`, so they are strings, and the default comparator compares strings by code unit: `'1'` sorts before `'2'`. A `Map<number, string>` keeps number keys and sorts them numerically.
  • Why pass a comparator field rather than a method call that returns a new comparator?
    The pipe re-sorts whenever the comparator reference changes. A stable field lets it return the cached array on checks where nothing changed; a method returning a fresh function forces a sort on every check.

saying these in an interview costs you the question

  • Believes KeyValuePipe preserves object insertion order by default
  • Thinks numeric object keys sort numerically in KeyValuePipe
  • Passes the signal itself to keyvalue instead of its value
  • Believes an impure pipe rebuilds its array on every check
  • Thinks the second argument is a sort direction string