1

Intro

I have a To Do List which leverages Angular Material's cdkDrag directive on elements inside of a cdkDropList. Marking items as 'to-do' or 'done' and vice versa is straightforward.

Problem

There's a level of detail in the user interaction, namely, a page refresh, which results in items not necessarily being in the order in which they were dragged.

Take this example in which a user is assigned 3 tasks: eat breakfast, eat lunch, eat dinner: enter image description here

The user doesn't like that priority and decides to tackle his/her eating assignments in this order instead. The drag and drop interactions take place and the To Do list is finalized as: enter image description here

On page refresh, that's where the gotcha kicks in. It goes back into the original state of:

eat breakfast, eat lunch, eat dinner

When it clearly should be:

eat dinner, eat breakfast, eat lunch

This observation holds true as well for items marked as done.

What is the missing piece I need to acknowledge the current state of this interaction. Do note, this implementation is based on How To Build an App With Drag and Drop With Angular

Thanks for your time.

markreyes
  • 1,249
  • 1
  • 13
  • 27
  • Are you saving the order somewhere, because refreshing the page will reset the state and everything will be lost. – ukn Jun 17 '20 at 19:56
  • No, just a CRUD operation to a fake back end to update the item. If I were to "save the order" what would be your idea to kickstart the saving of this? – markreyes Jun 17 '20 at 20:00
  • Either have a save button or a save triggered by the drop event, if you want to make sure the user keep what he did even if he refresh than I would suggest to save on every drop. Dispatch a save action with the updated list(which should keep the order) and add a order propertu to your backend. Then at the startup(OnInit of the component) you dispatch an action that init your data(get the todo list and everything else you need) – ukn Jun 17 '20 at 20:04

2 Answers2

5

You need convert you array of "strings" in an array of "object"

Imagine you has an array of items like

[{task:"eat breakfast",order:0},
 {task:"eat lunch",order:1},
 {task:"eat dinner",order:2}
]

In your drop function you need re-calculate the property order. using the typical drop function

drop(event: CdkDragDrop<any[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        event.previousIndex,
                        event.currentIndex);
      //if transfer, recalculate the order of previous (the list from drag)
      event.previousContainer.data.forEach((x,index)=>{
          x.order=index
      })
    }
    //always, recalculate the order of the container (the list to drag)
      event.container.data.forEach((x,index)=>{
          x.order=index
      })
  }

Then you need save the data in anyway (I think you can use the ngOnDestroy), when you recived the data don't forget sort by order

NOTE: In this SO there're are a cdkDragList using a formArray (only for sorting) if you want use a FormArray but it's not necesary, just with two arrays must be enougth

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • ERROR TypeError: Cannot assign to read only property 'order' of object '[object Object]' - it looks like the data inside of the event container is read only. – markreyes Jun 18 '20 at 14:43
  • The problem is the type in the argument event, must be "any" `drop(event: CdkDragDrop){..}`, see a working demo: https://stackblitz.com/edit/angular-vy2xfm?file=src/app/cdk-drag-drop-connected-sorting-example.ts (just corrected in answer). I can not reply your problem – Eliseo Jun 18 '20 at 14:55
  • Your previous example works as expected. But the working example from the original article leverages NGRX for state management. I think this is more complex than it appears as I'm trying to change immutable objects on recalculate. – markreyes Jun 23 '20 at 15:28
  • 1
    Only as suggest, try create a new array of object using "map" adding the property "order" and use this last array of object, or, if you has interface add the property order optional – Eliseo Jun 23 '20 at 17:01
  • Thanks, Eliseo. That final round of suggestions helped. I already had an interface, so plopping optional to the order attribute was added. I referenced the read only object in a new variable and then performed the map operation to a local mutable object inside of my drop() function. Do note, I'm doing state management via NgRx, so no backend ties into saving the state of the order. Thanks and here's the final result: https://stackblitz.com/edit/angular-tasklist-ng9 – markreyes Jun 25 '20 at 14:44
0

here's an approach I have encountered the same problem. I am creating a kanban board with to manage task and to change its status by drag and drop cdk so here's what I did:

html

Tasks To Do
<div class="example-container">
  <h2 style="text-align: center;">Tasks In Progress</h2>

  <div
    cdkDropList
    [cdkDropListData]="progresslist"
    class="example-list"
    (cdkDropListDropped)="drop($event)">
    <div   (click)=" openDialogAssign(item.taskId,item)" style="background-color: #78C0E0;"class="example-box" *ngFor="let item of progresslist" cdkDrag>        <app-boarditem 
      [item]="item" ></app-boarditem></div>
  </div>
</div>

<div class="example-container">
  <h2 style="text-align: center;">Tasks To Be Tested</h2>

  <div
    cdkDropList
    [cdkDropListData]="testedslist"
    class="example-list"
    (cdkDropListDropped)="drop($event)" >
    <div  (click)=" openDialogAssign(item.taskId,item)" style="background-color: #F8E290;" class="example-box" *ngFor="let item of testedslist" cdkDrag>        <app-boarditem 
      [item]="item" ></app-boarditem></div>
  </div>
</div>
<div class="example-container">
  <h2 style="text-align: center;">Tasks Done</h2>

  <div
    cdkDropList 
    [cdkDropListData]="donelist" 
    class="example-list"
    (cdkDropListDropped)="drop($event)">
    <div  (click)=" openDialogAssign(item.taskId,item)" style="background-color: rgb(173, 233, 173);" class="example-box" *ngFor="let item of donelist" cdkDrag>        <app-boarditem 
      [item]="item" ></app-boarditem></div>
  </div>
</div>

typescript:

drop(event: CdkDragDrop<any[]>) { if (event.previousContainer === event.container) { moveItemInArray( event.container.data, event.previousIndex, event.currentIndex );

} else {
  transferArrayItem(
    event.previousContainer.data,
    event.container.data,
    event.previousIndex,
    event.currentIndex
  );
  // console.log("ahayhayhy",event.container.id);
 console.log( event.container.data[event.currentIndex.valueOf()] );

 
  var taskupdatedStatus:any= event.container.data[event.currentIndex.valueOf()]
    // console.log("dattttttttttta",event.container.data);
    
    
    // x.itemId=index
  switch (event.container.id) {
    case "cdk-drop-list-0":
      {console.log("set to todo");
      
      this.taskservice.changeTaskStatus(taskupdatedStatus.taskId,"todo",taskupdatedStatus).subscribe(res=>{
        console.log("task updated to todo",res);
    console.log(taskupdatedStatus);
        
      })
      
    }
      

      break;
    case "cdk-drop-list-1":
        console.log("set to inprogress");
    console.log(taskupdatedStatus);
    this.taskservice.changeTaskStatus(taskupdatedStatus.taskId,"prog",taskupdatedStatus).subscribe(res=>{
      console.log("task updated to prog",res);
  console.log(taskupdatedStatus);
      
    })

        break;
    case "cdk-drop-list-2":
      console.log("set to test");
    console.log(taskupdatedStatus);
    this.taskservice.changeTaskStatus(taskupdatedStatus.taskId,"test",taskupdatedStatus).subscribe(res=>{
      console.log("task updated to test",res);
  console.log(taskupdatedStatus);
      
    })
      break;
        
    case "cdk-drop-list-3":
      console.log("set to DONE");
    console.log(taskupdatedStatus);
    this.taskservice.changeTaskStatus(taskupdatedStatus.taskId,"done",taskupdatedStatus).subscribe(res=>{
      console.log("task updated to done",res);
  console.log(taskupdatedStatus);
      
    })
      break;
          
                
    default:
      console.log("neiver");
      
      break;
  }
}

}

this is the line that i implemented to capture the element i just dropped so that i can access its attributs

var taskupdatedStatus:any= event.container.data[event.currentIndex.valueOf()]

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49