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:
With Angular 12 (or 13), the code is the exact same, but the UI doesn't update until the dragged item is dropped:
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
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