Ziehen und Ablegen in Angular

Rana Hasnain Khan 15 Februar 2024
Ziehen und Ablegen in Angular

Wir werden das Modul @angular/cdk/drag-drop einführen, um Drag & Drop in Angular zu erreichen.

Wir werden auch einige Beispiele für Drag & Drop in Angular vorstellen.

Ziehen und Ablegen in Angular

Das Modul @angular/cdk/drag-drop bietet Ihnen eine Möglichkeit, einfach und deklarativ Drag & Drop-Oberflächen zu erstellen. Dieses Modul unterstützt freies Ziehen, Sortieren innerhalb einer Liste, Übertragen von Elementen zwischen Listen, Animationen, Touch-Geräten, benutzerdefinierten Ziehgriffen, Vorschauen und Platzhaltern.

Einstieg

Zuerst importieren wir DragDropModule in NgModule in app.module.ts.

Hier ist der Code für app.module.ts.

# angular
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { DragDropModule } from "@angular/cdk/drag-drop";

import { AppComponent } from "./app.component";

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    CommonModule,
    DragDropModule
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

Nun importieren wir den folgenden Code in unsere app.component.ts.

# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
  CdkDrag,
  CdkDragStart,
  CdkDropList,
  CdkDropListGroup,
  CdkDragMove,
  CdkDragEnter,
  moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})

Jetzt exportieren wir unsere Klasse AppComponent und definieren Variablen in app.component.ts.

# angular
export class AppComponent {
  @ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
  @ViewChild(CdkDropList) placeholder: CdkDropList;

  public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

  public target: CdkDropList;
  public targetIndex: number;
  public source: CdkDropList;
  public sourceIndex: number;
  public dragIndex: number;
  public activeContainer;

  constructor(private viewportRuler: ViewportRuler) {
    this.target = null;
    this.source = null;
  }

Jetzt fügen wir die After-View-Initial-Funktion in app.component.ts hinzu.

# angular 
ngAfterViewInit() {
    let phElement = this.placeholder.element.nativeElement;

    phElement.style.display = "none";
    phElement.parentElement.removeChild(phElement);
  }

Jetzt erstellen wir eine add()-Funktion zu app.component.ts, um Elemente in einer Liste hinzuzufügen.

# angular
add() {
    this.items.push(this.items.length + 1);
  }

Jetzt erstellen wir eine Funktion zum shuffle unserer Liste in app.component.ts.

#angular
shuffle() {
    this.items.sort(function() {
      return 0.5 - Math.random();
    });
  }

Nun erstellen wir die Funktion dragMoved, die das Element in app.component.ts zieht.

# angular
dragMoved(e: CdkDragMove) {
    let point = this.getPointerPositionOnPage(e.event);

    this.listGroup._items.forEach(dropList => {
      if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
        this.activeContainer = dropList;
        return;
      }
    });
  }

Jetzt erstellen wir die Funktion dropListDropped in app.component.ts, die das Element nach dem Loslassen fallen lässt.

# angular
dropListDropped() {
    if (!this.target) return;

    let phElement = this.placeholder.element.nativeElement;
    let parent = phElement.parentElement;

    phElement.style.display = "none";

    parent.removeChild(phElement);
    parent.appendChild(phElement);
    parent.insertBefore(
      this.source.element.nativeElement,
      parent.children[this.sourceIndex]
    );

    this.target = null;
    this.source = null;

    if (this.sourceIndex != this.targetIndex)
      moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
    console.log("save here!", this.items);
  }

Jetzt erhalten wir den Punkt der Seite, den der Benutzer berührt hat, bevor er das Element abgelegt hat.

Wir werden die Funktion getPointerPositionOnPage in app.component.ts hinzufügen.

# angular
getPointerPositionOnPage(event: MouseEvent | TouchEvent) {
    const point = __isTouchEvent(event)
      ? event.touches[0] || event.changedTouches[0]
      : event;
    const scrollPosition = this.viewportRuler.getViewportScrollPosition();

    return {
      x: point.pageX - scrollPosition.left,
      y: point.pageY - scrollPosition.top
    };
  }

Jetzt werden wir die Funktion onClick hinzufügen, die ausgeführt wird, wenn der Benutzer auf Listenelemente klickt.

# angular
onClick(event) {
    console.log(event);
    alert("click!");
  }

Nun werden wir die folgenden Funktionen zu app.component.ts hinzufügen.

function __indexOf(collection, node) {
  return Array.prototype.indexOf.call(collection, node);
}

function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
  return event.type.startsWith("touch");
}

function __isInsideDropListClientRect(
  dropList: CdkDropList,
  x: number,
  y: number
) {
  const {
    top,
    bottom,
    left,
    right
  } = dropList.element.nativeElement.getBoundingClientRect();
  return y >= top && y <= bottom && x >= left && x <= right;
}

Unsere Datei app.component.ts sieht also wie folgt aus.

# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
  CdkDrag,
  CdkDragStart,
  CdkDropList,
  CdkDropListGroup,
  CdkDragMove,
  CdkDragEnter,
  moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  @ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
  @ViewChild(CdkDropList) placeholder: CdkDropList;

  public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

  public target: CdkDropList;
  public targetIndex: number;
  public source: CdkDropList;
  public sourceIndex: number;
  public dragIndex: number;
  public activeContainer;

  constructor(private viewportRuler: ViewportRuler) {
    this.target = null;
    this.source = null;
  }

  ngAfterViewInit() {
    let phElement = this.placeholder.element.nativeElement;

    phElement.style.display = "none";
    phElement.parentElement.removeChild(phElement);
  }

  add() {
    this.items.push(this.items.length + 1);
  }

  shuffle() {
    this.items.sort(function() {
      return 0.5 - Math.random();
    });
  }

  dragMoved(e: CdkDragMove) {
    let point = this.getPointerPositionOnPage(e.event);

    this.listGroup._items.forEach(dropList => {
      if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
        this.activeContainer = dropList;
        return;
      }
    });
  }

  dropListDropped() {
    if (!this.target) return;

    let phElement = this.placeholder.element.nativeElement;
    let parent = phElement.parentElement;

    phElement.style.display = "none";

    parent.removeChild(phElement);
    parent.appendChild(phElement);
    parent.insertBefore(
      this.source.element.nativeElement,
      parent.children[this.sourceIndex]
    );

    this.target = null;
    this.source = null;

    if (this.sourceIndex != this.targetIndex)
      moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
    console.log("save here!", this.items);
  }

  dropListEnterPredicate = (drag: CdkDrag, drop: CdkDropList) => {
    if (drop == this.placeholder) return true;

    if (drop != this.activeContainer) return false;

    let phElement = this.placeholder.element.nativeElement;
    let sourceElement = drag.dropContainer.element.nativeElement;
    let dropElement = drop.element.nativeElement;

    let dragIndex = __indexOf(
      dropElement.parentElement.children,
      this.source ? phElement : sourceElement
    );
    let dropIndex = __indexOf(dropElement.parentElement.children, dropElement);

    if (!this.source) {
      this.sourceIndex = dragIndex;
      this.source = drag.dropContainer;

      phElement.style.width = sourceElement.clientWidth + "px";
      phElement.style.height = sourceElement.clientHeight + "px";

      sourceElement.parentElement.removeChild(sourceElement);
    }

    this.targetIndex = dropIndex;
    this.target = drop;

    phElement.style.display = "";
    dropElement.parentElement.insertBefore(
      phElement,
      dropIndex > dragIndex ? dropElement.nextSibling : dropElement
    );
    this.placeholder._dropListRef.enter(
      drag._dragRef,
      drag.element.nativeElement.offsetLeft,
      drag.element.nativeElement.offsetTop
    );

    return false;
  };

    const point = __isTouchEvent(event)
      ? event.touches[0] || event.changedTouches[0]
      : event;
    const scrollPosition = this.viewportRuler.getViewportScrollPosition();

    return {
      x: point.pageX - scrollPosition.left,
      y: point.pageY - scrollPosition.top
    };
  }

  onClick(event) {
    console.log(event);
    alert("click!");
  }
}

function __indexOf(collection, node) {
  return Array.prototype.indexOf.call(collection, node);
}

function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
  return event.type.startsWith("touch");
}

function __isInsideDropListClientRect(
  dropList: CdkDropList,
  x: number,
  y: number
) {
  const {
    top,
    bottom,
    left,
    right
  } = dropList.element.nativeElement.getBoundingClientRect();
  return y >= top && y <= bottom && x >= left && x <= right;
}

Jetzt erstellen wir ein Frontend und fügen den folgenden Code in app.component.html ein.

<h1>Drag&Drop with a flex-wrap</h1>
<button (click)="add()">Add</button> <button (click)="shuffle()">Shuffle</button
><br />
<ul class="angular-list">
  <li *ngFor="let item of items">{{ item }}</li>
</ul>

<div class="amgular-container" cdkDropListGroup>
  <div
    cdkDropList
    [cdkDropListEnterPredicate]="dropListEnterPredicate"
    (cdkDropListDropped)="dropListDropped()"
  ></div>
  <div
    cdkDropList
    *ngFor="let item of items"
    [cdkDropListEnterPredicate]="dropListEnterPredicate"
    (cdkDropListDropped)="dropListDropped()"
  >
    <div
      cdkDrag
      class="angular-box"
      (cdkDragMoved)="dragMoved($event)"
      (click)="onClick($event)"
    >
      {{ item }}
    </div>
  </div>
</div>

Und wir werden unseren Styling-Code in app.component.css hinzufügen.

# angular
.angular-list {
  list-style-type: none;
  padding: 0;
}

.angular-list li {
  display: table-cell;
  padding: 4px;
}

.angular-container {
  display: flex;
  flex-wrap: wrap;
}

.angular-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  font-size: 30pt;
  font-weight: bold;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  position: relative;
  z-index: 1;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.angular-box:active {
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
  opacity: 0.6;
}

.cdk-drop-list {
  display: flex;
  padding-right: 15px;
  padding-bottom: 15px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(161, 41, 41, 0.2),
    0 8px 10px 1px rgba(141, 58, 58, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0.5;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

button {
  margin-right: 4px;
}

Jetzt sieht unsere Ausgabe so aus.

Ausgabe:

Drag-and-Drop-Ausgabe in Angularform

Rana Hasnain Khan avatar Rana Hasnain Khan avatar

Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.

LinkedIn