CdkDrag updating position
Asked Answered
P

2

8

I have multiple CdkDrag element that are rendered from a ngFor loop on an array in my component. when i remove one element i splice the array. Then some of the elements will update there position. how do i avoid that?

I tried to get the freeDragPosition of all the draggable element before deleting one of them, then to reset their position, but it did not work.

this is my app.component.html

<div class="container" id="container">
  <div *ngFor="let f of fields; let i = index;" 
    class="textField"
    [attr.data-guid]="f.guid"  
    (mouseenter)="onFieldHover($event)" 
    cdkDrag 
    cdkDragBoundary="#container"
    (cdkDragEnded)="onDragEnded($event)"
    [cdkDragFreeDragPosition]="f.position">
    <div class="textField-inner">
      <span style="color: hotpink; font-weight: bold">{{f.guid}}</span>
    </div>
    <div class="textField-options">
      <div class="textField-move" cdkDragHandle>
        <svg width="24px" fill="currentColor" viewBox="0 0 24 24">
          <path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"></path>
          <path d="M0 0h24v24H0z" fill="none"></path>
        </svg>
      </div>
      <div class="textField-remove">
        <i class="fas fa-trash-alt" (click)="onRemoveTextField(i)"></i>
      </div>
    </div>
  </div>
</div>
<button type="button" (click)="onTextFieldAdded()">Add a field</button>

and this is my app.component.ts

import { Component } from '@angular/core';
import {CdkDrag, CdkDragEnd} from '@angular/cdk/drag-drop';


@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.scss' ]
})
export class AppComponent  {
  name = 'Angular';
  fields: Field[] = [];
    draggables: CdkDrag<any>[] = [];


  onDragEnded(event: CdkDragEnd){
        if(!this.draggables.find(f => f == event.source)){
            this.draggables.push(event.source);
        }
    }

  onFieldHover(event: any){
        if(event.target.classList.contains('initialPosition')){
            event.target.classList.remove('initialPosition');
            event.target.style.transform = "translate3d(250px, 180px, 0px)";
        }
    }

  onTextFieldAdded() {
        let field = new Field();
        field.index = this.fields.length;
        field.guid = this.newGuid();

        this.fields.push(field);
    }

  onRemoveTextField(index: number){
    let positions : any[] =[];

        this.draggables.forEach(drag => {
            let pos = { guid: drag.element.nativeElement.getAttribute("data-guid"), pos: drag.getFreeDragPosition()  };
            positions.push(pos);
            console.log(pos);
        });

        this.fields.splice(index, 1);

    positions.forEach(p => {
      let newPos = {x: p.pos.x, y: p.pos.y};
      console.log(newPos);
      this.fields.find(f => f.guid == p.guid).position = newPos;
        });
    }

  newGuid(): string{
     return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
            return v.toString(16);
        });
  }

}

export class Field{
    index: number;
    guid: string;
    position: {x: number, y: number}
}


I would like for my element to remain in their current position when one of the element was deleted.

here's a .gif representing the problem element moving has i delete one

here's a stackblitz https://stackblitz.com/edit/angular-p3yfe7

Phototypy answered 17/10, 2019 at 13:25 Comment(2)
that positions.forEach(p => { ... }) inside your onRemoveTextField. What's the use of that? If I comment it out it works wellCollop
it was an attempt to set the position when i was deleting the elements.Phototypy
L
8

After some testing in your stackblitz I was able to reproduce your intended behavior, turns out when a cdkDrag is inside a ng For it will bind to the same list and when you remove one cdkDrag from the list it renders the template but with new positions to match the ngFor list order, to avoid that you can override the css style position from cdkDrag.

In your example just add position: absolute; to .textField css class.

Ledda answered 17/10, 2019 at 14:12 Comment(0)
C
2

The issue is that you did not specify a trackBy function in your *ngFor, this will cause the objects to re-render whenever the array changes:

You should create your *ngFor like this:

*ngFor="let f of fields; let i = index; trackBy: trackByField"

And update your component to have a trackByField method

 trackByField = (i: number, field: Field) => field.guid;

Then the items that have already been moved, remain in their positions, and non moved items will still re-order

stack

Collop answered 17/10, 2019 at 14:29 Comment(4)
I already tried the trackby function. it did not fixed the problem. The accepted answer is the solution.Phototypy
@FrancisGroleau just wondering though for future visitors, in what way does the provided stack I made not work for you?Collop
if you try to delete element in the stackblitz you provided it does still move the other elements, which is what im trying to avoid.Phototypy
@FrancisGroleau Ah, alright! I thought you only didn't want them to move if you moved them manuallyCollop

© 2022 - 2024 — McMap. All rights reserved.