'Angular cdkDragDrop behavior changed with update of Angular, UI no longer updates when dragging starts

We have a component which has items that can be dragged and dropped, we use the CDK drag/drop module for managing this.

Part of the functionality is to change a property of the underlying object when the user starts dragging. To get the UI to update we call ChangeDetectorRef.detectChanges().

When on version 9 of Angular this worked fine, however since updating to version 12.1 the UI no longer updates until the dragged item is dropped and I cannot find a way to get it working

Here is a simplified version of the component:

HTML:

<div cdkDropList>
<div cdkDrag 
 (cdkDragStarted)="onCdkDragStarted($event)"
 *ngFor="let step of steps">Step: {{step.id}}  {{step.text}}</div>
</div>

Code:

import { ChangeDetectorRef, Component} from '@angular/core';

@Component({
  selector: 'drag-poc',
  templateUrl: 'drag-poc.component.html',
  styles: []
})
export class DragPocComponent { 
  steps: any[] = [
    { id: 1, text: '' },
    { id: 2, text: '' },
    { id: 3, text: '' },
    { id: 4, text: '' },
    { id: 5, text: '' },
  ];

  constructor(private ref: ChangeDetectorRef){

  }

  onCdkDragStarted($event){
    // this is just a hack to get a reference to the array object that is being dragged
    const draggedItem = $event.source.__ngContext__[8].ngForOf[$event.source.__ngContext__[8].index];

    draggedItem.text = "MOVED";
    this.ref.detectChanges();
  }
}

With Angular 9 the UI updates once the dragging starts:

Dragging with updated UI

With Angular 12 (or 13), the code is the exact same, but the UI doesn't update until the dragged item is dropped:

Dragging with non-updated UI

Is there a way to get the UI to update once the dragging starts?

Stackblitz Angular 9: https://stackblitz.com/edit/angular-ivy-s4pmmm

StackBlitz Latest Angular: https://stackblitz.com/edit/angular-ivy-ztdufb

EDIT: the Latest Angular StackBlitz is updated to show the problem after the answer from @Eliseo

updated problem



Solution 1:[1]

I'm afraid that you need create a *cdkDragPreview. And a cdkDrag it's a bit ?@X##??!! because you need take acount several thing: the size of the drag, the point when you drag the element...

To take account all of this I usually create two variables

  style: any = null;
  offset:any=null

And the cdkDrag use (cdkDragMoved) and (mousedown) -you also need use (cdkDragStarted) and possible (cdkDragEnded) so your .html becomes like

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let movie of movies;let i=index" cdkDrag 
    [cdkDragData]="movie"
    (cdkDragStarted)="onDragStarted($event,i)"
    (cdkDragMoved)="onDragMove($event)"
    (cdkDragEnded)="onDragEnded($event,i)"
    
     (mousedown)="setStyle($event)" >{{movie.title}}{{movie.text}}
      <div  *cdkDragPreview class="example-box" [ngStyle]="style">
        {{movie.title}}{{movie.text}}
      </div>
    </div>
  </div>

And the .ts

  drop(event: CdkDragDrop<any[]>) {
    moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
  }
  onDragStarted(event:CdkDragStart<any>,index:number)
  {
      //you can use
      this.movies[index].text="moved"
      //or use
      event.source.data.text="moving...."

  }
  onDragEnded(event:CdkDragEnd<any>,index:number)
  {
    this.movies[index].text=""
  }
  setStyle(event: MouseEvent) {
    const rect = (event.target as HTMLElement).getBoundingClientRect();
    this.style = { width: rect.width + 'px', height: rect.height + 'px'}
    this.offset={x:event.offsetX,y:event.offsetY };
  }
  public onDragMove(event: CdkDragMove<any>): void {
    const el=(document.getElementsByClassName('cdk-drag-preview')[0])as any
    const xPos = event.pointerPosition.x - this.offset.x;
    const yPos = event.pointerPosition.y - this.offset.y;
    el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
  }

See in the stackblitz how I pass the "index" to the functions (onDragStarted) and (onDragEnded) to change the array of "object" (you needn't use some complex like your $event.source.__ngContext__[8].ngForOf[$eve....

NOTE: You can also check the property event.source or event.source.element to get the HTML element, you can also add a property cdkDragData to your Drag

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1