Skip to content

Instantly share code, notes, and snippets.

@ali-kamalizade
Last active October 13, 2022 20:41
Show Gist options
  • Save ali-kamalizade/84c1abe6165523b27a345eb509c9678a to your computer and use it in GitHub Desktop.
Save ali-kamalizade/84c1abe6165523b27a345eb509c9678a to your computer and use it in GitHub Desktop.
Angular CDK drag & drop
<div #formDropArea="cdkDropList" class="formFieldsDropArea" cdkDropList (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let formField of formFields; let index = index; trackBy: trackByIndex" class="columns" cdkDrag (cdkDragStarted)="onDragStart()">
<div class="column is-narrow">
<i class="fas fa-fw fa-grip-vertical" cdkDragHandle title="Drag to reorder"></i>
</div>
<div class="column">
<input [ngModel]="formField.title" (ngModelChange)="onFormFieldChange(formField, $event)"">
<button (click)="deleteField(index)">Delete</button>
</div>
</div>
</div>
<strong>Elements</strong>
<ul cdkDropList cdkDropListSortingDisabled [cdkDropListConnectedTo]="[formDropArea]">
<li *ngFor="let fieldType of choices" cdkDrag (cdkDragStarted)="onDragStart()">
{{ fieldType.title }}
</li>
</ul>
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, EventEmitter } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { addItemToArray, moveItemWithinArray } from '../pure-dnd.helpers';
type Field = {};
@Component({
selector: 'app-dnd',
templateUrl: './dnd.component.html',
styleUrls: ['./dnd.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DndComponent {
@Input() readonly formFields: Field[] = [];
@Output() readonly formFieldsChange = new EventEmitter<ReadonlyArray<Field>>();
readonly fieldChoices = [
{ type: 'textarea', title: 'Text field' },
{ type: 'number', title: 'Number field' },
{ type: 'date', title: 'Date field' }
];
constructor(private changeDetectorRef: ChangeDetectorRef) {}
onFormFieldChange(originalField: Field, updatedField: Field) {
this.formFieldsChange.emit(this.formFields.map((field) => (field === originalField ? updatedField : field)));
}
onDrop(dropEvent: CdkDragDrop<any>) {
this.changeDetectorRef.reattach();
const newFieldIndex = dropEvent.currentIndex;
if (dropEvent.previousContainer === dropEvent.container) {
this.formFieldsChange.emit(moveItemWithinArray(this.formFields, dropEvent.previousIndex, newFieldIndex));
} else {
this.formFieldsChange.emit(
addItemToArray(
{ label: '', type: this.fieldChoices[dropEvent.previousIndex].type, },
this.formFields,
newFieldIndex
)
);
}
}
onDragStart() {
this.changeDetectorRef.detach();
}
trackByIndex(index: number) {
return index;
}
}
/**
* Place an item from one list into another list. Unlike the native Angular CDK drag and drop, this function does not mutate the original array.
*/
export function addItemToArray<T>(newItem: Readonly<T>, targetArray: ReadonlyArray<T>, targetIndex: number): ReadonlyArray<T> {
const to = clamp(targetIndex, targetArray.length);
return [...targetArray.slice(0, to), newItem, ...targetArray.slice(targetIndex)];
}
/**
* Move an item within a list. Unlike the native Angular CDK drag and drop, this function does not mutate the original array.
*/
export function moveItemWithinArray<T>(currentArray: ReadonlyArray<T>, fromIndex: number, toIndex: number): ReadonlyArray<T> {
const from = clamp(fromIndex, currentArray.length - 1);
const to = clamp(toIndex, currentArray.length - 1);
if (from === to) {
return currentArray;
}
return currentArray.map((item, index) => {
if (index === from) {
return currentArray[to];
} else {
return index === to ? currentArray[from] : item;
}
});
}
/** Clamps a number between zero and a maximum. Copied from Angular CDK */
function clamp(value: number, max: number) {
return Math.max(0, Math.min(max, value));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment