SortableJS / ngx-sortablejs

Angular 2+ binding to SortableJS. Previously known as angular-sortablejs
https://sortablejs.github.io/ngx-sortablejs/
MIT License
466 stars 160 forks source link

Wrong display order on Angular 9 #189

Open snalesso opened 4 years ago

snalesso commented 4 years ago

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.

Etienne-Buschong commented 4 years ago

I have the same issue. Any update on this?

Etienne-Buschong commented 4 years ago

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.

dorthrithil commented 3 years ago

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.

onur-ozguzel commented 3 years ago

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

markanye commented 3 years ago

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

snalesso commented 3 years ago

Problem persists with:

jianxiangxun commented 2 years ago

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

jianxiangxun commented 2 years ago

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

equilerex commented 2 years ago

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;
  }
MichaelJFordham commented 2 years ago

Still experiencing this problem with:

Angular @ 13.3.2 ngx-sortablejs @ 11.1.0

MichaelJFordham commented 2 years ago

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.

Klapik commented 1 year ago

Any news?

kerimdragolj commented 4 hours ago

any update on this?