SortableJS / ngx-sortablejs

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

ngrx and sortable #83

Open serjo96 opened 6 years ago

serjo96 commented 6 years ago

Hello!

I use ngrx to save my array, and read from it, But how i understand, sortablejs + ngrx its nor good idea. If i wanna change array in ngrx i first need dispatch action, but sortablejs try splice this arrays and i take erorr this.target.splice is not a function

component

export class HomeComponent implements OnInit {

    console = console;
    todos$: Observable<any>;
    todo: string;
    todoDate: string;
    editing = false;
    indexToEdit: number | null;
    currentTime = new Date();

    constructor(private store: Store<any>) {}

    ngOnInit() {
        this.todos$ = this.store.select('todoReducer');
    }

}

template

<ul class="todo-list" [sortablejs]="todos$">
    <li     class="todo"
            *ngFor="let todo of todos$ | async; let i = index;"
            [ngClass]="{'todo--done': todo.done, 'todo--is-over': todo.date < currentTime, 'todo--close-date': todo.date.getDate()  - currentTime.getDate() <= 3 && todo.date > currentTime}"
    >
            <div class="todo__checkbox" (click)="toggleDone(todo, i)">
                <i  class="fa"
                    [ngClass]="{'fa-check-square': todo.done, 'fa-square-o': !todo.done}"
                    aria-hidden="true">
                </i>
            </div>
            <span class="todo__name">{{ todo.value }}</span>

            <span class="todo__date">{{todo.stringDate}}</span>
        <div class="todo__btns">
            <button (click)="editTodo(todo, i)"><i class="fa fa-pencil" aria-hidden="true"></i></button>
            <button (click)="deleteTodo(i)"><i class="fa fa-trash" aria-hidden="true"></i></button>
        </div>
    </li>
</ul>
smnbbrv commented 6 years ago

Hi @serjo96

the directive accepts either Array or FormArray. In your case you pass an observable [sortablejs]="todos$" which is of course wrong: observables are not yet supported (might be a good idea in future actually).

smnbbrv commented 6 years ago

@serjo96 Don't pass anything and it will stop attempts of slicing the things (e.g. <ul sortablejs>) but you would be left with the implementation of onUpdate / onAdd / onRemove on your own.

serjo96 commented 6 years ago

@smnbbrv if i understand, i need add atribute sortablejs and [sortablejsOptions]='MyupdateMethod'?

smnbbrv commented 6 years ago

yes, something similar to this, in particular [sortablejsOptions]='{ onUpdate: yourOnUpdate }' where onUpdate will be passed to the original library https://github.com/RubaXa/Sortable

smnbbrv commented 6 years ago

Please note that onUpdate manages the updates of the current list only. If you drag / drop from / to different lists you need to use onAdd and onRemove additionally

serjo96 commented 6 years ago

@smnbbrv yea, thaat good, but how i now may take changed array?

smnbbrv commented 6 years ago

@serjo96 you have an array that is rendered with ngFor; just take this array in onUpdate event...

See https://smnbbrv.github.io/angular-sortablejs-demo/custom-options or its implementation https://github.com/smnbbrv/angular-sortablejs-demo/blob/master/src/app/examples/sortable-with-options/sortable-with-options.component.ts

serjo96 commented 6 years ago

yea. i see this exaples, but the problem if i write like this[sortablejsOptions]='{onUpdate: test()}' its mean i wanna call function all time and i take infinity loop, how i may call function and give him my array? Or add array like this, but i take not changed array

eventOptions: SortablejsOptions = {
        onUpdate: () =>   this.store.dispatch(  updateList(this.state)  );
    };

Have any another way to take array?)

smnbbrv commented 6 years ago

Please post a plunkr with what you tried. It's hard to say what you are doing wrong until I have a full picture

serjo96 commented 6 years ago

if add my array like argument, function be called infinity, so i do like this, and take all time old array https://stackblitz.com/edit/angular-jhdafj?file=app%2Fapp.component.html

serjo96 commented 6 years ago

So, i change tactic and return to old realisation with model bind, all work fine , but its strange. console.log(this.state) give me my new sorted array, but when i give this new array to my action and dispatch, in reducer i tike old array. Update stackblitz


<ul class="todo-list" [sortablejs]="state"  [sortablejsOptions]='eventOptions'>
    <li     class="todo"
            *ngFor="let todo of state  let i = index;"
            [ngClass]="{'todo--done': todo.done, 'todo--is-over': toDate(todo.date) < currentTime, 'todo--close-date': toDate(todo.date).getDate()  - currentTime.getDate() <= 3 && todo.date > currentTime}"
    >

//give me new sorted array
eventOptions: SortablejsOptions = {
        onUpdate: () =>   console.log(this.state)
    };

//give me my old array

eventOptions: SortablejsOptions = {
        onUpdate: () =>   {
            let arr = this.state;
            console.log(arr)
            this.store.dispatch(updateList(arr))

        }
    };
smnbbrv commented 6 years ago

@serjo96 this is a correct approach I would say. It works fine I guess?

serjo96 commented 6 years ago

@smnbbrv Not really. html sort successfully passes without errors, but here the array comes old, in the example that I attached, it is evident that in the console after the call of the onupdate comes the old array, if i try use another function in on update.

https://stackblitz.com/edit/angular-jhdafj?file=app%2Fapp.component.html

smnbbrv commented 6 years ago

ok, got it.

See https://stackblitz.com/edit/angular-1kl9cz?file=app/app.component.ts

This works somehow but I am not sure this is a really nice way to deal with the problem.

The problem itself is that after the array gets updated the changes are propagated to the store and of course they come back to the component. setTimeout is forcing angular to rerender the things asynchronously, so, somehow it works.

Cannot promise anything better for now. I am thinking of v3 where this could be addressed as well.

smnbbrv commented 6 years ago

please don't close it I renamed it for keeping in mind the main idea

serjo96 commented 6 years ago

i dont know what i do, but all works fine without setTiemout 😂 maybe it because i use splice, i remove all splice and all work!

trumbitta commented 6 years ago

@serjo96 shouldn't you pass todo$ | async here:

<ul class="todo-list" [sortablejs]="todos$">

?

rowanhogan commented 6 years ago

For anyone (like me) who found themselves here looking for an ngrx example working together with this lib, look no further!

example.component.ts
public items: Store<any[]> = this.store.select(selectItems);

public sortableOptions: SortablejsOptions = {
  // ...options,
  onStart: e => this.onDrag(e),
  onUpdate: e => this.onDrop(e)
};

constructor(private store: Store<AppState>) {}

private onDrag(event: any) {
  const { oldIndex, newIndex } = event;

  return this.store.dispatch(
    new DragStart({ oldIndex, newIndex })
  );
}

private onDrop(event: any) {
  const { oldIndex, newIndex } = event;

  return this.store.dispatch(
    new DragEnd({ oldIndex, newIndex })
  );
}
example.component.html
<div [sortablejs]="items | async" [sortablejsOptions]="sortableOptions">
  <div *ngFor="let item of items | async">
    <child-component [item]="item"></child-component>
  </div>
</div>
TasosBak commented 5 years ago

@rowanhogan Thanks a lot but you are not providing the actual actions and the reducers that will actually do the work in the store! Are you working with entity or not?

Michelangelo1984 commented 4 years ago

Hi there, my contribution to this issue. I have recently upgraded to Angular 9 and my old version of sortablejs did not work anymore so I upgraded. However this code worked fine in version 2.7.0 of angular-sortablejs.

<div class="list-group" [sortablejs]="todos | async">
   <app-todo *ngFor="let element of todos | async; index as i"
      [todo]=todo
      [index]=i>
    </app-todo>
</div>

With the update to ngx-sortablejs 3.1.4 it does not work correctly anymore. Drag and drop does work but there seems to be another update after you drop the item causing a weird 'jump' in the element you dropped. Also, it sometimes keeps the items in the correct order and sometimes it just shuffles. After some digging in the library it seems that the target array gets out of sync with the observable at some point.

It is mysterious to me why it worked perfectly fine in the old version.