[Fixed] How to reorder grouped items with ion-reorder

Issue

I’m trying to use ion-reorder to reorder some items in a page. These item are organized in groups, so a group has several items. I want to organize these items inside the group, but more importantly I want to be able to change the item from a group to another.

I’m facing 2 problems currently:

  • I can’t update the array, consequently the item orders are not show correctly when changed.
  • I can’t move a item outside it’s group, consequently I can’t move to another group.

html

<ion-content>   
    <ion-list *ngFor='let group of groupArray'>
        <ion-reorder-group (ionItemReorder)="doReorder($event)" disabled="false">
            <ion-list-header>{{group.name}}</ion-list-header>           
            <ion-item *ngFor='let item of group.items'>
                <ion-reorder slot="start"></ion-reorder>
                <ion-label>
                    {{item.name}}
                </ion-label>
            </ion-item>
        </ion-reorder-group>
    </ion-list>     
</ion-content>

typescript

interface Item {
    id: number,
    name: string
}
interface Group {
    name: string,
    items: Item[]
}
interface GroupArray extends Array<Group>{}

...

private groupArray: GroupArray = [
    {
        name: 'FirstGroup',
        items: [
            {
                id: 1,
                name: 'FirstItem'
            },{
                id: 2,
                name: 'SecondItem'
            },{
                id: 3,
                name: 'ThirdItem'
            }
        ]
    },
    {
        name: 'SecondGroup',
        items: [
            {
                id: 4,
                name: 'FourthItem'
            },{
                id: 5,
                name: 'FifthItem'
            },{
                id: 6,
                name: 'SixthItem'
            }
        ]
    }       
];

/** Reorder */
doReorder(ev: CustomEvent<ItemReorderEventDetail>) {
    this.groupArray = ev.detail.complete(this.groupArray);
}

I tried to move the <ion-reorder-group> to several positions, but this is the only one I managed to move the correct items. If I move up, above <ion-list>, the whole list becomes draggable and I can’t move the item.

I understand that this is happening because the <ion-reorder-group> is created 2 times, but I can’t think in a way to create only one time and maintain the same aspect, which is exactly like the documentation page that I linked. The difference is that in there they load the items individually, and the groups are just another item that can be drag around.

The other problem I believe is related to how complete() works. Although it expects any[] as a parameter, I don’t think it was made to support a complex structure, just a simple array.

How can I solve these two problems?


I’m using Ionic 5 with Angular 10

StackBlitz

Solution

For the reording issue, you have two things

  • You are sending the wrong items to the complete function, it should be the rendered items (groupArray[index].items), not the root object itself (groupArray).
  • The ion-list-header should be above the ion-reorder-group, so Ionic won’t treat it as a child of the ion-reorder-group and mess up the reording logic for the array.

Even though the example in the attached link for the ion-reorder is using one big ion-reorder-group and the ion-list-header to add some list headers, but this won’t work if you need to update the array using the complete.

You can do something like this to make it work:

doReorder(ev: CustomEvent<ItemReorderEventDetail>, groupId: number) {
  let groupToChangeIndex = this.groupArray.findIndex(
    group => group.id === groupId
  );
  this.groupArray[groupToChangeIndex].items = ev.detail.complete(
    this.groupArray[groupToChangeIndex].items
  );
}

HTML:

<ion-content>
  <ion-list *ngFor='let group of groupArray'>
    <ion-list-header>{{group.name}}</ion-list-header>
    <ion-reorder-group (ionItemReorder)="doReorder($event, group.id)" disabled="false">
      <ion-item *ngFor='let item of group.items'>
        <ion-reorder slot="start"></ion-reorder>
        <ion-label>
          {{item.name}}
        </ion-label>
      </ion-item>
    </ion-reorder-group>
  </ion-list>
</ion-content>

At the same time, you can’t move an ion-item from one group to another.
What I can think of for handling this issue is to have one big ion-reorder-group with the ion-list-header, and do the reording manually. Accessing the target property from the event that was sent to the doReorder function, then you can access the children, filter only the ion-item then reflect the sorting to the array (even though it is a very ugly workaround).


Update

I actually thought of a better solution. You can create a new array that contains all the items flattened. Then when you want to submit you changes, there will be a function that will create the new order for you. You can check the following Stackblitz: https://stackblitz.com/edit/ionic-5-angular-10-start-template-zasssd?file=src/app/tab1/tab1.page.ts

constructor() {
  this.allItemsFlattened = this.groupArray.flatMap((group, index) => [
    { isHeader: true, index: index, name: group.name },
    ...group.items
  ]);
}

/** Reorder objects in array */
doReorder(ev: CustomEvent<ItemReorderEventDetail>) {
  this.allItemsFlattened = ev.detail.complete(this.allItemsFlattened);
}

getRightOrder() {
  const rightOrder = this.allItemsFlattened.reduce((accumlator, item) => {
    if (item.isHeader) {
      const { id, name, ...rest } = this.groupArray[item.index];
      accumlator.push({ id, name, items: [] });
    } else {
      accumlator[accumlator.length - 1].items.push(item);
    }
    return accumlator;
  }, []);
  console.log(rightOrder);
}

Leave a Reply

(*) Required, Your email will not be published