问题
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
回答1:
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.
回答2:
<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:
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>
回答4:
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>
回答5:
Try below piece of code:
<div *ngFor="let item of object | keyvalue: 0">
{{item.key}} : {{item.value}}
</div>
回答6:
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>
回答7:
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,
...
]
...
})
来源:https://stackoverflow.com/questions/52793944/angular-keyvalue-pipe-sort-properties-iterate-in-order