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

ngrx and sortable

Open serjo96 opened this issue 7 years ago • 20 comments
trafficstars

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>

serjo96 avatar Jan 21 '18 18:01 serjo96

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 avatar Jan 21 '18 18:01 smnbbrv

@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.

smnbbrv avatar Jan 21 '18 18:01 smnbbrv

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

serjo96 avatar Jan 21 '18 18:01 serjo96

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 avatar Jan 21 '18 18:01 smnbbrv

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

smnbbrv avatar Jan 21 '18 18:01 smnbbrv

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

serjo96 avatar Jan 21 '18 19:01 serjo96

@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

smnbbrv avatar Jan 21 '18 19:01 smnbbrv

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?)

serjo96 avatar Jan 21 '18 21:01 serjo96

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

smnbbrv avatar Jan 22 '18 07:01 smnbbrv

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 avatar Jan 22 '18 16:01 serjo96

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))

        }
    };

serjo96 avatar Jan 22 '18 21:01 serjo96

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

smnbbrv avatar Jan 23 '18 12:01 smnbbrv

@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

serjo96 avatar Jan 23 '18 12:01 serjo96

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 avatar Jan 23 '18 18:01 smnbbrv

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

smnbbrv avatar Jan 23 '18 18:01 smnbbrv

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!

serjo96 avatar Jan 23 '18 19:01 serjo96

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

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

?

trumbitta avatar May 14 '18 09:05 trumbitta

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>

rowanhogan avatar Jun 07 '18 07:06 rowanhogan

@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?

TasosBak avatar Jan 19 '19 15:01 TasosBak

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.

Michelangelo1984 avatar Feb 10 '20 15:02 Michelangelo1984