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
here's a stackblitz https://stackblitz.com/edit/angular-p3yfe7
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With