173

When using the Angular keyvalue pipe to iterate over an object's properties as follows:

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

I have experienced an issue where the properties were not iterated in the order expected. And this comment suggests that I am not the only one to experience this issue:

How to loop over object properties with ngFor in Angular

Can someone advise what determines the order of iteration when using the keyvalue pipe please and how to force a specific iteration order? My ideal order of iteration is the order in which the properties were added.

Thanks

16 Answers 16

351

According to the Angular documentation, the keyvalue pipe sorts the items by key order by default. You can provide a comparer function to change that order, and sort the items according to the key, to the value, or to the entry order of the properties in the object.

The following comparer functions sort the items in various orders:

// Preserve original property order
originalOrder = (a: KeyValue<number,string>, b: KeyValue<number,string>): number => {
  return 0;
}

// Order by ascending property value
valueAscOrder = (a: KeyValue<number,string>, b: KeyValue<number,string>): number => {
  return a.value.localeCompare(b.value);
}

// Order by descending property key
keyDescOrder = (a: KeyValue<number,string>, b: KeyValue<number,string>): number => {
  return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
}

when applied to the keyvalue pipe:

<div *ngFor="let item of object | keyvalue: originalOrder">
  {{item.key}} : {{item.value}}
</div>

<div *ngFor="let item of object | keyvalue: valueAscOrder">
  {{item.key}} : {{item.value}}
</div>

<div *ngFor="let item of object | keyvalue: keyDescOrder">
  {{item.key}} : {{item.value}}
</div>

See this stackblitz for a demo.


Supplying a constant or null instead of a valid comparer function preserves the entry order of the object properties, like originalOrder does, but it causes an exception (see this stackblitz):

<!-- The following syntaxes preserve the original property order -->
<div *ngFor="let item of object | keyvalue: 0">
<div *ngFor="let item of object | keyvalue: 374">
<div *ngFor="let item of object | keyvalue: null">

<!-- but they cause an exception (visible in the console) -->
ERROR TypeError: The comparison function must be either a function or undefined

Moreover, using that syntax twice in the template does not display the items at all. Therefore, I would not recommend it. Please note that supplying undefined as the comparer function does not cause any exception but does not change the default behavior: the items are sorted by key value.

Sign up to request clarification or add additional context in comments.

11 Comments

What if I don'y want them to be ordered at all?
@ConnorsFan thank you so much, you are a life saver. I wrote keyvalue: 0 and they are in the original order.
keyvalue: 0 no longer works in Angular 7. Just create a empty function: sortNull() {} and then set keyvalue: sortNull
@ConnorsFan Thanks, I went with originalOrder() { return 0; } based on a comment in in one of the other answers and no longer get the console error.
The originalOrder function can be simplified to originalOrder = () => 0
|
33
<div *ngFor="let item of object | keyvalue: 0">
  {{item.key}} : {{item.value}}
</div>

directly write and you will get the data in sameorder as it is in the json

keyvalue: 0

3 Comments

I was please with that solution, then I opened the console and so a lot of error. ERROR TypeError: The comparison function must be either a function or undefined
create a function asIs() { return 0; }. The function can be added as: keyvalue: asIs
Also... You will need implement @PrasoonSrivastava suggestion... A "0" value don't works on build because type errors...
31

Yes, by default the keyvalue pair sorting data in ascending order.

To keep it unsorted modify as:

keyvalue: 0

Final code:

<div *ngFor="let item of object | keyvalue:0">
  {{item.key}}:{{item.value}}
</div>

But for angular 7 or above, you need to put an empty function to keep data unsorted.

<div *ngFor="let item of object | keyvalue: unsorted">
      {{item.key}}:{{item.value}}
    </div>

In your .ts file just put this empty function.

  unsorted() { }

Hope it's helpful.

3 Comments

It will work, but will also cause core.js:6241 ERROR TypeError: The comparison function must be either a function or undefined ;). At least in Angular 10.
Worked for me with after adding attributes : unsorted(a: KeyValue<number, string>, b: KeyValue<number, string>): number { return 0; }
ts: unsorted(a: any, b: any): number { return 0; } html: <div *ngFor="let item of object | keyvalue: unsorted"> {{item.key}}:{{item.value}} </div>
23

To keep the object order you can use

keepOrder = (a, b) => {
    return a;
}

Let's say you have

wbs = {
"z": 123,
"y": 456,
"x": 789,
"w": 0#*
}

If you use keyvalue you get alphabetical order by key

w 0#*
x 789
y 456
z 123

applying :keepOrder you keep object order

<ion-item *ngFor="let w of wbs | keyvalue: keepOrder">
    <ion-label>{{ w.key }}</ion-label>
    <ion-label>{{ w.value }}</ion-label>
</ion-item>

Comments

10

This is same as accepted answer, but it has more complex object so It can help somebody How to sort by custom index field and using keyval pipe.

In angular component:

myObject = {
    "key1": { val:"whateverVal1", code:"whateverCode1", index: 1},
    "key2": { val:"whateverVal2", code:"whateverCode2", index: 0},
    "key3": { val:"whateverVal3", code:"whateverCode3", index: 2}
}

Sorting function in component:

indexOrderAsc = (akv: KeyValue<string, any>, bkv: KeyValue<string, any>): number => {
        const a = akv.value.index;
        const b = bkv.value.index;

        return a > b ? 1 : (b > a ? -1 : 0);
    };

in template:

<div *ngFor="let x of myObject | keyvalue:indexOrderAsc">
    ...
</div>

2 Comments

Shouldn’t the parameter in sorting function be KeyValue<string,any>?
excellent solution "any" solved my problem I was using this inside datatable
9

I prefer overriding default keyvalue behavior by extending it in my pipe. Then I use my pipe instead of keyvalue in my templates.

For Angular >= 13

import {Pipe, PipeTransform} from '@angular/core';
import {KeyValuePipe} from '@angular/common';

const keepOrder = (a, b) => a;

// This pipe uses the angular keyvalue pipe. but doesn't change order.
@Pipe({
  name: 'defaultOrderKeyvalue'
})
export class DefaultOrderKeyvaluePipe extends KeyValuePipe implements PipeTransform {

  transform(value: any, ...args: any[]): any {
    return super.transform(value, keepOrder);
  }

}

For Angular < 13

import {Pipe} from '@angular/core';
import {KeyValuePipe} from '@angular/common';

const keepOrder = (a, b) => a;

// This pipe uses the angular keyvalue pipe but doesn't change order.
@Pipe({
  name: 'defaultOrderKeyvalue'
})
export class DefaultOrderKeyvaluePipe extends KeyValuePipe {

  public transform(value, compareFn = keepOrder) {
    return super.transform(value, compareFn);
  }

}

2 Comments

I can't seem to override keyvalue this way, Type 'KeyValue<unknown, unknown>[]' is not assignable to type 'KeyValue<string, any>[]'.
@MahmoudEzzat see my post that will solve your problem ;-)
5

Angular 17

The most suggested answer that DOESN'T WORK because types don't match:

keyvalue: 0

The shortest way:

originalOrder = () => 0;

In my case, this is the solution because of eslint rules:

public readonly originalOrder = (): number => 0;

Comments

4

Try below piece of code:

<div *ngFor="let item of object | keyvalue: 0">
  {{item.key}} : {{item.value}}
</div>

1 Comment

This works, but its leaving an exception in the console
3

I think this is a cleaner solution. Uncomment the return statement that needs to give the right order:

interface KeyValue<K, V> {
  key: K,
  value: V
}

// Order by descending property key
  keyOrder = (aA: KeyValue<string,any>, bB: KeyValue<string,any>): number => {
    const a = aA.value.index;
    const b = bB.value.index;
    //return a > b ? 1 : (b > a ? -1 : 0); // ASCENDING
    return a > b ? -1 : (b > a ? 1 : 0); // DESCENDING
  }

USE CASE

<div *ngFor="let x of y | keyvalue: keyOrder;">
   {{ x.key  }} indexes: {{ x.value.index }}
</div>

Comments

2

Yes, Object properties iterates randomly since it doesn't get stored as array in memory. However you can sort by properties.

If you want to iterate by insertion position, you need to create one extra property like index and set the timestamp and sort by index.

Below is the pipe you can use to control the sorting and iteration

Pipe

import {Pipe, PipeTransform} from 'angular2/core';

@Pipe({name: 'values'})
export class ValuesPipe implements PipeTransform {
    transform(value: any, args?: any[]): Object[] {
        let keyArr: any[] = Object.keys(value),
            dataArr = [],
            keyName = args[0];

        keyArr.forEach((key: any) => {
            value[key][keyName] = key;
            dataArr.push(value[key])
        });

        if(args[1]) {
            dataArr.sort((a: Object, b: Object): number => {
                return a[keyName] > b[keyName] ? 1 : -1;
            });
        }

        return dataArr;
    }
}

Usage

<div *ngFor='#item in object | values:"keyName":true'>...</div>

Comments

2

Make a pipe to set default to unordered:

// src/app/pipes/new-key-value.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { KeyValuePipe } from '@angular/common';
import { KeyValueDiffers } from '@angular/core';
const unordered = (a,b)=>0

@Pipe({
  name: 'newkeyvalue'
})

// This pipe uses the angular keyvalue pipe. but defaults to unordered.
export class NewKeyValuePipe implements PipeTransform {
  
  constructor(public differs: KeyValueDiffers){};
  public transform (value, compareFn = unordered){
    let pipe =  new KeyValuePipe(this.differs);
    return pipe.transform(value, compareFn)
  }
}

links:

Can I call an Angular2 pipe from a pipe?

https://github.com/angular/angular/blob/8.2.14/packages/common/src/pipes/keyvalue_pipe.ts#L25-L89

Template usage:

// src/app/some-component/some-component.html
<div *ngFor="let x of myObject | newkeyvalue>
</div>

register in the module.ts

// src/app/app.module.ts
import { NewKeyValuePipe } from '@/pipes/new-key-value.pipe.ts'
...
@NgModule({
  declarations: [
    ...
    NewKeyValuePipe,
    ...
  ]
...
})

3 Comments

I'm unsure why but this code cause freezes in my app when keyvalue'ing form group values. When I extended KeyValuePipe instead of creating new instance in transform() it works fine.
How did you extend KeyValuePipe inside of the transform()?
He extended instead of not inside of
1

The way to keep the items unsorted , as stated many times before here, is indeed:

<div *ngFor="let item of object | keyvalue: unsorted">

But the function for unsorted should return 1 (always prefer first element; whereas 0 means both elements are equal, and therefore ordering might be implemented differently), and nowadays be protected as well:

protected readonly unsorted = () => 1;

Comments

1

With Angular 13.

<div *ngFor="let item of object | keyvalue: 0">
  {{item.key}} : {{item.value}}
</div>

keyvalu: 0 

is not working but empty unsorted method is working if you dont want to perform any sort of operations on existing array object.

<div *ngFor="let item of object | keyvalue: unsorted">
      {{item.key}}:{{item.value}}
    </div>

In your .ts file just put this empty function.

  unsorted() { }

Comments

1

When using the pipe keyvalue: unsorted I have encountered an issue with the change detection not triggering when only the order of the key in the map changed so instead of using the pipe I found the simpler solution is to convert the map to array manually like this: map((map) => Object.entries(map).map(([key, value]) => ({ key, value }))),

Comments

1

Simply implement your own pipe (with correct typing) that takes a map as input and gives you back an array in the same order:

@Pipe({
  name: 'keepOrderKeyValue'
})
export class KeepOrderKeyValuePipe implements PipeTransform {

  transform<K, V>(input: ReadonlyMap<K, V>): KeyValue<K, V>[] {
    return Array.from(input, ([key, value]) => ({ key, value }))
  }
}

Or if you want to support multiple types like a Record/Map or handle undefined, you can implement it like this:

@Pipe({
  name: 'keepOrderKeyValue'
})
export class KeepOrderKeyValuePipe {

  transform<K extends keyof any, V>(input: ReadonlyMap<K, V> | Record<K, V> | undefined): Array<KeyValue<K, V>> {
    if (input instanceof Map) {
      return Array.from(input, ([key, value]) => ({ key, value }))
    }

    return input
        ? Object.entries(input).map(([key, value]) => ({ key: key as K, value }))
        : []
  }
}

Usage:

<div *ngFor="let item of object | keepOrderKeyValue">
  {{item.key}} : {{item.value}}
</div>

Comments

1

Simply pass null as argument

<div *ngFor="let item of object | keyvalue: null; track i.key">
  {{item.key}}:{{item.value}}
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.