When I'm using the Angular's KeyValuePipe in my template, the key pipe-results is losing its type.
Here is an example.
app.component.ts:
export class AppComponent {
someObject: Record<`${'key1' | 'key2'}`, {myCustomValue: string}> =
{key1: {myCustomValue: 'value1'}, key2:{myCustomValue:'value2'}}
getKeyValueElement(key: 'key1' | 'key2', value: {myCustomValue: string}){
console.log(key, value)
}
}
Then in my app.component.html:
<div *ngFor="let keyValueElement of someObject | keyvalue"
(click)="getKeyValueElement(keyValueElement.key, keyValueElement.value)">
</div>
And here is the error I get:

The app refuses to compile since there is incompatibility between the getKeyValueElement key parameter - which is 'key1' | 'key2', and the key from the pipe-result which is string.
Now, there are 2 different ways that I know how to solve it:
$any() in the template to avoid type checking - Not a
recommended practice.compareFn to the KeyValuePipe with
the exact requested types in the signature. this will effect the types rendered in the
key&value pipe-results. This is also not a good solution in most
cases when you don't really need a custom compareFn.So, I'm looking for another solution. In my eyes, this error should not appear in the first place.
That because of the signature of transformof the keyvalue pipe.
One of the entries is :
transform<K extends number, V>( input: Record<K, V>, compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number): Array<KeyValue<string, V>>;
It matches type Record<K extends string | number | symbol, T> = { [P in K]: T; }
You can see Record has number as key.
So the solution would be to use a proper map :
const someObject: Map<('key1' | 'key2'), { myCustomValue: string }> =
new Map([['key1', { myCustomValue: 'value1' }], ['key2', { myCustomValue: 'value2' }]])
I created the following pipe which uses the existing pipe logic but does not convert the keys to strings but instead retains the original types of the keys.
import { KeyValue, KeyValuePipe } from "@angular/common";
import { Pipe, PipeTransform, ɵdefaultKeyValueDiffers } from "@angular/core";
@Pipe({
name: 'KeyValue',
standalone: true,
})
export class KeyValueTypedPipe implements PipeTransform {
keyValuePipe = new KeyValuePipe(ɵdefaultKeyValueDiffers);
transform<V extends Record<string, unknown>>(
input: V,
compareFn = (_a: V, _b: V) => 0, // Bonus: retain order of keys
) {
return this.keyValuePipe.transform(
input,
compareFn as unknown as Parameters<typeof this.keyValuePipe.transform>[1]
) as Array<KeyValue<keyof V, V[keyof V]>>;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With