ngx-sortablejs icon indicating copy to clipboard operation
ngx-sortablejs copied to clipboard

Wrong display order on Angular 9

Open snalesso opened this issue 4 years ago • 15 comments

Description

Only a single draggable div as follows:

<div [sortablejs]="selectedItems" [sortablejsOptions]="selectedItemsOptions">
    <button *ngFor="let item of selectedItems; let i = index;" [class.active]="item.isSelected" (click)="remove(item)">{{i + 1}}° {{item.label}}</button>
</div>

If an item is moved to the last position, when another item is addeded via code this.selectedItems.push(item) the list of items visualized is sorted wrongly. The array items order is correct, but the visual representation of it is not. I guess it's some incompatibility between sortablejs/ngx-sortablejs and Angular 9, since if I try to reproduce the issue on a Angular 8 project it doesn't happen.

How to reproduce

I uploaded a sample project here. The code is very simple, it's just a new Angular application with changes on app.component.ts and app.component.html.

If you don't want to download the sample you can just follow these steps:

  1. Create a new project with: ng new <project_name>
  2. Install dependencies: npm i -S ngx-sortablejs sortablejs npm i -D @types/sortablejs
  3. Import SortablejsModule in AppModule.ts
  4. Edit only app.component.ts and app.component.html according to the sample linked above.

Environment

I tested this on Angular 9.0.7 and Angular 8.2.14. Versions mentioned above are those indicated in <app-root ng-version="">. The issue shows up only on Angular 9.

snalesso avatar Mar 24 '20 13:03 snalesso

I have the same issue. Any update on this?

Etienne-Buschong avatar Jun 02 '20 15:06 Etienne-Buschong

A temporary solution is to use the onUpdate hook of the sortable-js options object and overwrite the sortablejs bindingTarget in it. This forces Angular's change detection to run and re-render the ngFor correctly.

// We are in .ts component file
// For this example binding target is called "data"
 const sortablejsOptions = {
   onUpdate: () => {
      const originalData = {...this.data};
      this.data = {};
      setTimeout(() => {
         this.data = originalData;
      )}
   }
}

The data will then be rendered correctly.

Note: I also tried to omit the Angular2-Binding and use sortablejs directly with Angular, but this leads to the same problem. So maybe this problem is also/more related to the original sortablejs library.

Etienne-Buschong avatar Jun 03 '20 09:06 Etienne-Buschong

I guess this is the same issue that Dragula faces. It seems to be related to the Ivy compiler. I experienced the exact same issue for Dragula and Sortable.

Here is a solution for Dragula: https://stackoverflow.com/questions/63532041/ng2-dragula-after-adding-new-item-its-getting-displayed-at-the-top/63609337#63609337

Can we somehow apply the same for Sortable? For now Iv'e switched to Dragula but I would love to switch back as soon as this bug is gone or can be worked around.

dorthrithil avatar Feb 17 '21 10:02 dorthrithil

Hi there;

Can you please try version 3.1.3 and let me know if it' s working for you too or not?

I' m using 3.1.3 on Angular 10, it' s working fine. But later versions have this bug & i cannot update ngx-sortable because of that.

Related topic which i opened is: https://github.com/SortableJS/ngx-sortablejs/issues/202

onur-ozguzel avatar Feb 17 '21 12:02 onur-ozguzel

Same problem here! Angular 10 with ngx-sortablejs 10.1.0.

markanye avatar Feb 27 '21 22:02 markanye

Problem persists with:

  • Angular @ 11.2.3
  • ngx-sortablejs @ 11.1.0

snalesso avatar Mar 01 '21 16:03 snalesso

Same problem here! Angular 13.1.0 with ngx-sortablejs 11.1.0.

jianxiangxun avatar Mar 01 '22 07:03 jianxiangxun

A temporary solution is to use the onUpdate hook of the sortable-js options object and overwrite the sortablejs bindingTarget in it. This forces Angular's change detection to run and re-render the ngFor correctly.

// We are in .ts component file
// For this example binding target is called "data"
 const sortablejsOptions = {
   onUpdate: () => {
      const originalData = {...this.data};
      this.data = {};
      setTimeout(() => {
         this.data = originalData;
      )}
   }
}

The data will then be rendered correctly.

Note: I also tried to omit the Angular2-Binding and use sortablejs directly with Angular, but this leads to the same problem. So maybe this problem is also/more related to the original sortablejs library.

i think requestAnimationFrame is better than setTimeout

jianxiangxun avatar Mar 08 '22 03:03 jianxiangxun

Having the same issue. In my case, i am unable/willing to overwrite/rebind the data since its a FormArray originating from the parent component (and i do not want to sever that connection).

As a really rough temporary fix, i'm just hiding the whole element with ngIf for a split second (adding new items is done in a modal window in my solutin, so the flash is not too noticeable)

// we forcefully remove and re-render the sortable element before adding new tiles
  hardRefresh() {
    // relevant only if the order was changed beforehand
    if (this.orderChanged) {
      this.forceOrderRefresh = true;
      this.orderChanged = false;
       this.cdr.detectChanges();
      setTimeout(() => {
        this.forceOrderRefresh = false;
         this.cdr.detectChanges();
      }, 0);
    }
  }

added to the element:

      <div
      *ngIf="!forceOrderRefresh"
      [sortablejs]="mediaArray.controls"
      [sortablejsOptions]="sortablejsOptions" 
      class="ao-media-upload__multi-file-draggable-area"
    >

and to make sure that the trick is only used in case drag was used, ive added "orderChanged " into dragend:

  onDragEnd(event) {  
    this.cdr.detectChanges();
    this.updateArrayValidators();
    this.orderChanged = true;
  }

equilerex avatar Mar 29 '22 13:03 equilerex

Still experiencing this problem with:

Angular @ 13.3.2 ngx-sortablejs @ 11.1.0

MichaelJFordham avatar Apr 13 '22 09:04 MichaelJFordham

For anyone suffering with this problem, I found this worked for me:

  sortableOptions: SortableOptions = {
    ...other options...
    onUpdate: () => {
      // Replace this.arrayContent with your data
      this.arrayContent = [
        ...this.arrayContent
      ];
    },
  };

It seems like re-assigning the data triggers Angular to re-render the *ngFor section, which means items are displayed in the correct order.

MichaelJFordham avatar Apr 13 '22 12:04 MichaelJFordham

Any news?

Klapik avatar Mar 13 '23 15:03 Klapik