Skip to content

feat(select): add compareWith Input for object value comparison #11965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 15, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions demos/src/select/pages/page-one/page-one.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
</ion-select>
</ion-item>

<ion-item>
<ion-label>Hair Color</ion-label>
<ion-select [(ngModel)]="hairColor" okText="Okay" cancelText="Dismiss" [compareWith]="compareFn">
<ion-option *ngFor="let o of hairColorData" [value]="o">{{o.text}}</ion-option>
</ion-select>
</ion-item>

<ion-item>
<ion-label>Gaming</ion-label>
<ion-select [(ngModel)]="gaming" okText="Okay" cancelText="Dismiss">
Expand Down Expand Up @@ -147,6 +154,13 @@
</ion-select>
</ion-item>

<ion-item>
<ion-label>Skittles</ion-label>
<ion-select [(ngModel)]="skittles" multiple="true" okText="Okay" cancelText="Dismiss" [compareWith]="compareFn">
<ion-option *ngFor="let o of skittlesData" [value]="o">{{o.text}}</ion-option>
</ion-select>
</ion-item>

<ion-item>
<ion-label>Disabled</ion-label>
<ion-select multiple disabled="true">
Expand Down
32 changes: 32 additions & 0 deletions demos/src/select/pages/page-one/page-one.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export class PageOne {
petAlertOpts: any;
petData: any;
pets: Array<string>;
hairColorData: any;
hairColor: any;
skittlesData: any;
skittles: Array<any>;
notifications: string = 'mute_1';
rating: number = 2;

Expand All @@ -31,9 +35,37 @@ export class PageOne {
{ text: 'Honey Badger', value: 'honeybadger' },
];

this.hairColorData = [
{ text: 'Brown', value: 'brown' },
{ text: 'Blonde', value: 'blonde' },
{ text: 'Black', value: 'black' },
{ text: 'Red', value: 'red' }
];

// Pre-selected object with different object reference
this.hairColor = { text: 'Brown', value: 'brown' };

this.skittlesData = [
{ text: 'Red', value: 'red' },
{ text: 'Orange', value: 'orange' },
{ text: 'Yellow', value: 'yellow' },
{ text: 'Green', value: 'green' },
{ text: 'Purple', value: 'purple' }
];

// Pre-selected object with different object reference
this.skittles = [
{ text: 'Red', value: 'red' },
{ text: 'Purple', value: 'purple' }
];

this.pets = ['cat', 'dog'];
}

compareFn(option1: any, option2: any) {
return option1.value === option2.value;
}

monthChange(val: any) {
console.log('Month Change:', val);
}
Expand Down
51 changes: 48 additions & 3 deletions src/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ import { SelectPopover, SelectPopoverOption } from './select-popover-component';
* };
* ```
*
* ### Object Value References
*
* When using objects for select values, it is possible for the identities of these objects to
* change if they are coming from a server or database, while the selected value's identity
* remains the same. For example, this can occur when an existing record with the desired object value
* is loaded into the select, but the newly retrieved select options now have different identities. This will
* result in the select appearing to have no value at all, even though the original selection in still intact.
*
* Using the `compareWith` `Input` is the solution to this problem
*
* ```html
* <ion-item>
* <ion-label>Employee</ion-label>
* <ion-select [(ngModel)]="employee" [compareWith]="compareFn">
* <ion-option *ngFor="let employee of employees" [value]="employee">{{employee.name}}</ion-option>
* </ion-select>
* </ion-item>
* ```
*
* ```ts
* compareFn(e1: Employee, e2: Employee): boolean {
* return e1 && e2 ? e1.id === e2.id : e1 === e2;
* }
* ```
*
* @demo /docs/demos/src/select/
*/
@Component({
Expand Down Expand Up @@ -187,6 +212,20 @@ export class Select extends BaseInput<any> implements OnDestroy {
*/
@Input() selectedText: string = '';

/**
* @input {Function} The function that will be called to compare object values
*/
@Input()
set compareWith(fn: (o1: any, o2: any) => boolean) {
if (typeof fn !== 'function') {
throw new Error(`compareWith must be a function, but received ${JSON.stringify(fn)}`);
}
this._compareWith = fn;
}

private _compareWith: (o1: any, o2: any) => boolean;


/**
* @output {any} Emitted when the selection was cancelled.
*/
Expand Down Expand Up @@ -451,9 +490,15 @@ export class Select extends BaseInput<any> implements OnDestroy {
if (this._options) {
this._options.forEach(option => {
// check this option if the option's value is in the values array
option.selected = this.getValues().some(selectValue => {
return isCheckedProperty(selectValue, option.value);
});
if (this._compareWith) {
option.selected = this.getValues().some(selectValue => {
return this._compareWith(selectValue, option.value);
});
} else {
option.selected = this.getValues().some(selectValue => {
return isCheckedProperty(selectValue, option.value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is isCheckedProperty is the default, compare function? this way we don't have to duplicate code. does it make sense?

});
}

if (option.selected) {
this._texts.push(option.text);
Expand Down