Currently the SelectItem
interface is defined as such:
export interface SelectItem {
label: string;
value: any;
}
This is very limiting when trying to create anything beyond a very basic template. Would be great to be able to pass in an object of values that then can be used in a custom template, dropdown for example.
I have found that it's not really a limitation having to use the SelectItem
interface. You can use a pipe to transform the data. For example:
interface Person {
firstName: string;
lastName: string;
id: number;
}
@Pipe({ name: 'toSelectItem' })
class ToSelectItemPipe implements PipeTransform {
public transform(people: Person[]): SelectItem[] {
if (!people) return undefined;
return people.map(p => ({ label: p.firstName + ' ' + p.lastName, value: p.id }));
}
}
const people= [
{ firstName: 'Joe', lastName: 'Jones', id: 1 },
{ firstName: 'Mary', lastName: 'Robinson', id: 2 },
{ firstName: 'Bob', lastName: 'Smith', id: 3 },
{ firstName: 'Suzy', lastName: 'Sorenson', id: 4 }
];
<p-dropdown [options]="people | toSelectItem"></p-dropdown>
This is a clever way to transform data to match the interface. This still leaves you with only access to a 'label' and 'value' when constructing a template (on a dropdown for example). Sure you can concatenate some sort of big string with all the data you want on the label but you're then very limited to how you can present it via a template.
Perhaps this would be reasonable?
export interface SelectItem {
label: string;
value: any;
[propName: string]: any;
}
Sorry, I've been meaning to get back to you. You can already do what you want. This works, for example:
interface Person {
firstName: string;
lastName: string;
id: number;
}
// An intersection type that combines Person and SelectItem.
type PersonSelectItem = Person & SelectItem;
@Pipe({ name: 'toSelectItem' })
class ToSelectItemPipe implements PipeTransform {
public transform(people: Person[]): PersonSelectItem[] {
if (!people) return undefined;
// Create an object that has all of the Person properties AND the SelectItem properties.
return people.map(p => Object.assign({ }, p, { label: p.firstName + ' ' + p.lastName, value: p.id });
}
}
const people= [
{ firstName: 'Joe', lastName: 'Jones', id: 1 },
{ firstName: 'Mary', lastName: 'Robinson', id: 2 },
{ firstName: 'Bob', lastName: 'Smith', id: 3 },
{ firstName: 'Suzy', lastName: 'Sorenson', id: 4 }
];
<p-dropdown [options]="people | toSelectItem">
<ng-template let-person pTemplate="item">
<dl>
<dt>First Name</dt>
<dd>{{person.firstName}}</dd>
<dt>Last Name</dt>
<dd>{{person.lastName}}</dd>
</dl>
</ng-template>
</p-dropdown>
@battmanz ah, yea that's definitely a nice way to do it with the current interface.
@battmanz are you able to get this to work for an editable dropdown?
Ideally I'd like to be able to put any[]
in [options]
and specify a value and label field. Maybe something like this:
<p-dropdown [options]="myArray" [valueField]="'id'" [labelField]="'name'">
</p-dropdown>
Using the SelectItem
interface doesn't really gain me anything. I almost never have a pre-formatted list of SelectItem
from the server, so this would simplify a lot and avoid having to map data back and forth.
As a workaround (sort of) you can use any[]
currently as long as the object has a value
(and label
if you don't plan on using a template). You're still forced to use value
as your binding though.
@JohnnyHandsaw I see your point. Because we're creating the options on the fly in the pipe, we're not holding onto a reference. So if the user changes the value
, it won't be reflected in the Person
object (in my example). You could probably work around that by using a getter/setter for the value
like so:
// Create an object that has all of the Person properties AND the SelectItem properties.
return people.map(p => Object.assign({ }, p, {
label: p.firstName + ' ' + p.lastName,
get value() {
return p.id;
},
set value(newValue) {
p.id = newValue;
}
});
I haven't tried that yet, but something like that should work.
@mdonato7 I made almost exactly those changes you're asking for. The only difference is that I called the attributes labelKey
and valueKey
. My pull request is 2702. However, I wasn't sure if PrimeNG would accept it because of their policy of not accepting new features from the community, plus I found out I could use a pipe. So I ended up declining my own pull request. If enough people wanted it, maybe I could try resubmitting it.
We'll reconsider if more users demand it. Thank you.
Has this been solved? I am using pdropdown to display several objects and I want 'label' to appear in the options (this work), but I want the value of every option to be 'id'. This does not work and I do not want to add an extra pipe in every component.ts file, as I have many select tags for many different options.
Why does dataKey not work? Is there a way around this directly in my html file?
Yes I have a generic pipe for this, works with any object. Will post when I have time.
Here it is.
import {Pipe, PipeTransform} from '@angular/core';
import {SelectItem} from 'primeng/primeng';
@Pipe({ name: 'dropdownPipe' })
export class DropdownPipe implements PipeTransform {
/**
* Performs the specified action for each element in an array.
* @param array The array of objects to convert
* @param labelKey The key of the object to use as the label (can be string or array of strings, if Array then first element is the separator)
* @param valueKey Optional key of the object to use as the value, default is the object
* @param placeholder Optional placeholder element
*/
transform(array: any[], labelKey: string | string[], valueKey?: string, placeholder?: string): SelectItem[] {
if (!array||!labelKey) return undefined;
let tmpArray;
if (labelKey instanceof Array) {
if (labelKey && valueKey) {
tmpArray = array.map( (arrayValue) => ({ label: arrayValue[labelKey[1]], value: arrayValue[valueKey] }));
}else if (labelKey && !valueKey) {
tmpArray = array.map( (arrayValue) => ({ label: arrayValue[labelKey[1]], value: arrayValue }));
}
for (let j = 2; j < labelKey.length; j++) {
if (labelKey && valueKey) {
tmpArray = array.map( (arrayValue, i) => ({ label: tmpArray[i].label + labelKey[0] + arrayValue[labelKey[j]], value: arrayValue[valueKey] }));
}else if (labelKey && !valueKey) {
tmpArray = array.map( (arrayValue, i) => ({ label: tmpArray[i].label + labelKey[0] + arrayValue[labelKey[j]], value: arrayValue }));
}
}
}else {
if (labelKey && valueKey) {
tmpArray = array.map(arrayValue => ({ label: arrayValue[labelKey], value: arrayValue[valueKey] }));
}else if (!labelKey && valueKey) {
tmpArray = array.map(arrayValue => ({ label: arrayValue, value: arrayValue[valueKey] }));
}else if (labelKey && !valueKey) {
tmpArray = array.map(arrayValue => ({ label: arrayValue[labelKey], value: arrayValue }));
}else {
tmpArray = array.map(arrayValue => ({ label: arrayValue, value: arrayValue }));
}
}
if (placeholder) {
tmpArray.unshift({label: placeholder, value: null});
}
return tmpArray;
}
}
@digaus
Hi, can you please post an example how to use this pipe in html?
Thanks!
Yes, once the pipe configured, what does the HTML look like?
Thank you @digaus 馃憤
HTML example for dropdownPipe:
<p-dropdown [options]="people | dropdownPipe:[' ','firstName','lastName']:'id">
Most helpful comment
Here it is.