Search code examples
javascriptionic-frameworkangular8ionic5ion-select

ion-select (multiple) not working with array of objects using compareWith property


I am facing issue with ion-select(array objects) (multiple select). Two items are already selected on page load, but when you open the drop-down, none of the items are checked. Here is stackblitz link to reproduce this issue.

https://stackblitz.com/edit/ionic-angular-v5-kie1wd

I am using Ionic(5.26.0) and angular(8.2.14) for my project.

<ion-item>
  <ion-label>Users(Multi)</ion-label>
  <ion-select multiple="true" [(ngModel)]="selectedMultipleEmployee" [compareWith]="compareFn"
    (ionChange)="multiChange()">
    <ion-select-option *ngFor="let user of users" [value]="user">{{user.first + ' ' + user.last}}
    </ion-select-option>
  </ion-select>
</ion-item>
compareFn(e1: User, e2: User): boolean {
    return e1 && e2 ? e1.id === e2.id : e1 === e2;
}

How select-box looks like on page load

open multiple select issue


Solution

  • TL;DR

    Your compareWith(e1, e2) function must return true if e2 is an array and a single element e1 is in e2, for example:

    // defined outside of class
    const compareWith = (e1, e2) => {
        // e2 may be an array, if multiple="true"
        if (Array.isArray(e2) ) {
            return e2.indexOf(e1) !== -1;
        }
        // fallback to single element comparison
        return e1 && e2 ? e1.id === e2.id : e1 === e2;
    }
    
    class MyPage {
    ...
        compareFn = compareWith;
    ...
    }
    

    Important: also, define your function compareWith outside of the class, otherwise it does not work

    Background

    I've had the same issue after upgrading from Ionic3 to Ionic5, and I've debugged the ion-select code to see what's causing this issue.

    Here's the function that determines if the an option must be checked or unchecked:

    const compareOptions = (currentValue, compareValue, compareWith) => {
        if (typeof compareWith === 'function') {
            return compareWith(currentValue, compareValue);
        }
        else if (typeof compareWith === 'string') {
            return currentValue[compareWith] === compareValue[compareWith];
        }
        else {
            return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
        }
    };
    

    The compareValue is an array and the final else clause handles that case if compareWith is not provided. But since you provide your own compareWith function, then it has to be able to check if a single items belongs to that array.

    Here's the actual code that worked for my specific case (comparing items by field code):

    const compareWith = (f1, f2) => {
      if (Array.isArray(f2)) {
        if (!f1 || !f1.code) {
          return false;
        }
        return f2.find(val => val && val.code === f1.code);
      }
      return f1 && f2 ? f1.code === f2.code : f1 === f2;
    };
    

    Update: Added a note that compareWith needs to be defined outside of the class.