Képzeljen el egy felhasználói felületet, ahol az elemek mozgatása, rendezése vagy épp áthelyezése egy másik listába olyan természetesen történik, mintha a fizikai valóságban tenné. A „húzd és engedd”, vagy ahogy angolul nevezzük, a drag-and-drop funkcionalitás mára alapvető elvárássá vált a modern webes alkalmazásokban. Legyen szó feladatlistákról, fájlkezelőkről, táblázatokról vagy akár játékokról, ez az interaktív elem rendkívül sokat dob a felhasználói élményen (UX).
Bár a drag-and-drop megvalósítása elsőre bonyolultnak tűnhet, az Angular ökoszisztémája szerencsére kínál egy elegáns és hatékony megoldást: az Angular CDK-t (Component Dev Kit). Ez a cikk részletesen bemutatja, hogyan építhetünk fel egy robusztus és testreszabható húzd-és-engedd felületet az Angular CDK segítségével, lépésről lépésre, a kezdeti beállítástól a haladó funkciókig.
Mi az az Angular CDK?
Az Angular CDK nem más, mint egy olyan eszközkészlet, amely magas szintű, viselkedés alapú alapkomponenseket (primitives) biztosít az Angular komponensek építéséhez, anélkül, hogy előre meghatározott UI stílusokat erőltetne. Gondoljunk rá úgy, mint egy funkcionális alapra, amelyre ráépíthetjük a saját vizuális megjelenésünket. A CDK modulok széles skáláját kínálja, többek között:
- Accessibility: Eszközök a jobb akadálymentességhez.
- Overlay: Felugró ablakok, menük kezelése.
- Scrolling: Gördítési viselkedés testreszabása.
- Drag and Drop: Ez az, ami minket a legjobban érdekel. Lehetővé teszi az elemek húzását és eldobását a DOM-ban.
A CDK legnagyobb előnye, hogy elvonatkoztat a vizuális megjelenéstől, így teljes szabadságot ad a fejlesztőknek a stílusok és a dizájn kialakításában. Ezáltal a megvalósítás rendkívül rugalmas és könnyedén illeszthető bármilyen projekt egyedi igényeihez.
Előfeltételek és Telepítés
Mielőtt belevágunk a kódolásba, győződjünk meg róla, hogy rendelkezünk egy működő Angular projekttel. Ha még nincs, az Angular CLI segítségével könnyedén létrehozhat egyet:
ng new my-drag-app
cd my-drag-app
Ezután telepítenünk kell az Angular CDK-t. Ez magában foglalja az összes CDK modult, beleértve a drag-and-drop funkcionalitást is. A telepítéshez futtassuk az alábbi parancsot a projekt gyökérkönyvtárából:
ng add @angular/cdk
Ez a parancs hozzáadja a @angular/cdk
csomagot a package.json
fájlunkhoz, és automatikusan beállítja a szükséges konfigurációkat. A telepítés után készen állunk arra, hogy bevezessük a drag-and-drop funkciót az alkalmazásunkba.
Az Alapvető Húzd-és-Engedd Funkció: cdkDrag
A drag-and-drop alapja a DragDropModule
. Ezt a modult importálnunk kell abba az Angular modulba (általában az AppModule
-be vagy egy funkcionális modulba), ahol használni szeretnénk a drag-and-drop funkciót.
Nyissuk meg az src/app/app.module.ts
fájlt, és adjuk hozzá a DragDropModule
-t az imports
tömbhöz:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { DragDropModule } from '@angular/cdk/drag-drop'; // <-- Ezt importáljuk
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
DragDropModule // <-- Ezt adjuk hozzá
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Most, hogy a modul importálva van, használhatjuk a cdkDrag
direktívát bármely HTML elemen, amelyet húzhatóvá szeretnénk tenni. Tekintsünk meg egy egyszerű példát:
src/app/app.component.html
:
<div class="container">
<h2>Alapvető Húzd-és-Engedd Példa</h2>
<div class="box" cdkDrag>
Húzz engem!
</div>
</div>
src/app/app.component.css
:
.container {
padding: 20px;
text-align: center;
}
.box {
width: 150px;
height: 150px;
background-color: #4CAF50;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
cursor: grab;
border-radius: 8px;
margin: 20px auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
/* Az Angular CDK által hozzáadott osztályok */
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
Ha most elindítjuk az alkalmazásunkat (ng serve
), láthatjuk, hogy a „Húzz engem!” feliratú doboz szabadon mozgatható az oldalon. A cdkDrag
direktíva gondoskodik mindenről, ami a húzási művelethez szükséges: az egéresemények kezelésétől kezdve a pozíciófrissítésig. Fontos megjegyezni, hogy a CDK alapértelmezésben nem ad stílust az elemeknek; a fent látható CSS csak a doboz alap megjelenéséért felel, és néhány CDK által hozzáadott osztályhoz kapcsolódó vizuális visszajelzést ad.
Listák Rendezése: cdkDropList és moveItemInArray
A drag-and-drop gyakori felhasználási módja a listák elemeinek rendezése. Az Angular CDK ezt is rendkívül egyszerűvé teszi a cdkDropList
direktívával és a cdkDropListDropped
eseménnyel.
Definiáljunk egy listát az app.component.ts
fájlban:
import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-drag-app';
movies = [
'Forrest Gump',
'A remény rabjai',
'Ponyvaregény',
'A Gyűrűk Ura: A Gyűrű Szövetsége',
'Mátrix',
'Interstellar'
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
Ezután módosítsuk az app.component.html
fájlt, hogy megjelenítse a filmlistát, és tegye azt rendezhetővé:
<div class="container">
<h2>Filmek rendezése</h2>
<div cdkDropList class="movie-list" (cdkDropListDropped)="drop($event)">
<div class="movie-box" *ngFor="let movie of movies" cdkDrag>
{{ movie }}
</div>
</div>
</div>
Ne felejtsük el frissíteni a CSS-t a listák megjelenéséhez:
/* ... (Előző CSS kód) ... */
.movie-list {
width: 300px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: flex;
flex-direction: column;
background: white;
border-radius: 4px;
overflow: hidden;
margin: 20px auto;
}
.movie-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: grab;
background: white;
font-size: 14px;
}
.movie-box:last-child {
border: none;
}
.movie-box.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.movie-box:active {
cursor: grabbing;
}
.movie-list.cdk-drop-list-dragging .movie-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
Ebben a példában a cdkDropList
direktíva jelöli ki a droppable területet (azaz azt a konténert, ahová az elemeket dobhatjuk). A (cdkDropListDropped)="drop($event)"
eseményfigyelő hívja meg a drop
metódust, amikor egy elem befejezi a húzási műveletet a listában. A drop
metóduson belül a moveItemInArray
segédfüggvényt használjuk, amely az @angular/cdk/drag-drop
csomagból származik. Ez a függvény automatikusan átrendezi a tömb elemeit a megadott indexek alapján, és a CDK frissíti a DOM-ot ennek megfelelően. Ez a megoldás rendkívül hatékony és minimalista.
Elemek Áthelyezése Listák Között: cdkDropListConnectedTo és transferArrayItem
Gyakran van szükségünk arra, hogy ne csak rendezzük az elemeket egy listán belül, hanem áthelyezzük őket különböző listák közé. Az Angular CDK ezt is zökkenőmentesen kezeli a cdkDropListConnectedTo
tulajdonság és a transferArrayItem
segédfüggvény segítségével.
Képzeljünk el két listát: „Elintézendő” és „Kész”.
src/app/app.component.ts
:
import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; // <-- transferArrayItem is
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-drag-app';
todo = [
'Vásárolj élelmiszert',
'Fizesd be a számlákat',
'Küldj e-mailt Jánosnak',
'Tervezd meg a hétvégét'
];
done = [
'Készítsd el a prezentációt',
'Kódolj 2 órát'
];
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
// Elem mozgatása ugyanazon a listán belül
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
// Elem áthelyezése listák között
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
}
}
src/app/app.component.html
:
<div class="container">
<h2>Feladatkezelő</h2>
<div class="task-lists-wrapper">
<div class="task-list">
<h3>Elintézendő</h3>
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of todo" cdkDrag>
{{ item }}
</div>
</div>
</div>
<div class="task-list">
<h3>Kész</h3>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of done" cdkDrag>
{{ item }}
</div>
</div>
</div>
</div>
</div>
src/app/app.component.css
:
/* ... (Előző CSS kód) ... */
.task-lists-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 20px; /* Helyköz a listák között */
}
.task-list {
width: 300px;
border: solid 1px #ccc;
min-height: 200px;
border-radius: 4px;
overflow: hidden;
background: white;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1);
}
.task-list h3 {
text-align: center;
background-color: #f0f0f0;
padding: 10px;
margin: 0;
border-bottom: solid 1px #eee;
}
.example-list {
min-height: 60px;
display: flex;
flex-direction: column;
padding: 10px;
}
.example-box {
padding: 15px 10px;
border: solid 1px #eee;
margin-bottom: 8px;
color: rgba(0, 0, 0, 0.87);
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: grab;
background: #fff;
font-size: 14px;
border-radius: 4px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
}
.example-box:last-child {
margin-bottom: 0;
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
/* Kiemelés a listák közötti áthúzáskor */
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.cdk-drop-list-receiving {
background: #e0f2f7; /* Világoskék háttér, amikor a lista fogadó állapotban van */
}
.cdk-drop-list-dragging .example-box {
opacity: 0; /* A húzott elem eredeti helyén eltűnik */
}
.cdk-drag-placeholder {
background: #ccc;
border: dotted 3px #999;
min-height: 50px;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
opacity: 1; /* Alapértelmezett placeholder láthatóvá tétele */
}
Ebben a kibővített példában a következő kulcsfontosságú elemeket figyelhetjük meg:
#todoList="cdkDropList"
és#doneList="cdkDropList"
: Template reference változókat definiálunk az egyescdkDropList
direktívákhoz. Ezekre azért van szükség, hogy a listák egymásra tudjanak hivatkozni.[cdkDropListData]="todo"
és[cdkDropListData]="done"
: Ezzel a bindinggel adjuk meg acdkDropList
-nek, hogy mely adatforráshoz tartozik. Fontos, hogy ez ne egy statikus érték, hanem a komponensben definiált tömb legyen.[cdkDropListConnectedTo]="[doneList]"
és[cdkDropListConnectedTo]="[todoList]"
: Ez a tulajdonság határozza meg, hogy az adottcdkDropList
mely máscdkDropList
-ekkel van összekötve, azaz melyekből tud fogadni vagy melyekbe tud elemeket áthelyezni. Egy tömböt vár, amely a csatlakoztatni kívánt listák referenciáit tartalmazza.drop(event: CdkDragDrop<string[]>)
: Ez a metódus most már ellenőrzi, hogy az elem ugyanazon a listán belül maradt-e (event.previousContainer === event.container
) vagy áthelyezték-e egy másik listába.- Ha ugyanazon a listán belül maradt, a
moveItemInArray
-t hívjuk meg. - Ha másik listába került, a
transferArrayItem
-et használjuk. Ez a segédfüggvény áthelyezi az elemet az egyik tömbből a másikba, és frissíti a DOM-ot.
- Ha ugyanazon a listán belül maradt, a
Ez a struktúra lehetővé teszi, hogy komplex drag-and-drop interakciókat építsünk ki, ahol az elemek szabadon mozoghatnak különböző, egymással összefüggő konténerek között.
Haladó Funkciók és Testreszabás
Az Angular CDK a fent bemutatott alapvető funkciókon túl számos lehetőséget kínál a drag-and-drop viselkedés testreszabására.
Egyéni Placeholder: cdkDragPlaceholder
Amikor egy elemet húzunk, a CDK alapértelmezésben egy „placeholder” elemet hagy maga után az eredeti helyén, ami egy üres, az eredeti elemmel megegyező méretű doboz. Ezt testreszabhatjuk a <ng-template cdkDragPlaceholder>
segítségével.
<div class="example-box" *ngFor="let item of todo" cdkDrag>
{{ item }}
<ng-template cdkDragPlaceholder>
<div class="custom-placeholder">Ide dobd!</div>
</ng-template>
</div>
.custom-placeholder {
background: #f0f8ff; /* Világoskék háttér */
border: dotted 2px #007bff; /* Kék szaggatott szegély */
padding: 15px 10px;
margin-bottom: 8px;
color: #007bff;
text-align: center;
font-weight: bold;
border-radius: 4px;
}
Egyéni Előnézet: cdkDragPreview
Az elem húzásakor a CDK alapértelmezésben egy „preview” elemet hoz létre, amely az eredeti elem másolata. Ezt is testreszabhatjuk a <ng-template cdkDragPreview>
segítségével.
<div class="example-box" *ngFor="let item of todo" cdkDrag>
{{ item }}
<ng-template cdkDragPreview>
<div class="custom-preview">
<span>Húzom: {{ item }}</span>
</div>
</ng-template>
</div>
.custom-preview {
background: #007bff;
color: white;
padding: 10px 15px;
border-radius: 4px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
opacity: 0.9;
transform: rotate(3deg); /* Egy kis vizuális effekt */
}
Fogófelület Definiálása: cdkDragHandle
Ha csak az elem egy bizonyos részét szeretnénk húzhatóvá tenni, használhatjuk a cdkDragHandle
direktívát. Ezt egy gyermekelemre helyezzük, és csak az ezen elemre kattintva lesz húzható a szülő elem.
<div class="example-box" *ngFor="let item of todo" cdkDrag>
<div class="drag-handle" cdkDragHandle>☰</div>
<span>{{ item }}</span>
</div>
.drag-handle {
cursor: grab;
margin-right: 10px;
font-size: 1.5em;
color: #555;
}
.drag-handle:active {
cursor: grabbing;
}
.example-box {
display: flex;
align-items: center;
}
Húzás Letiltása: cdkDragDisabled és cdkDropListDisabled
Az elemek vagy a listák húzását/dobását programozottan is tilthatjuk a [cdkDragDisabled]="true"
vagy [cdkDropListDisabled]="true"
tulajdonságok segítségével. Ez hasznos lehet feltételes logikák esetén, például ha egy felhasználónak nincs jogosultsága az elemek mozgatására.
<div class="example-box" [cdkDragDisabled]="isDragDisabled" cdkDrag>
{{ item }}
</div>
<div cdkDropList [cdkDropListDisabled]="isDropDisabled" class="example-list" (cdkDropListDropped)="drop($event)">
...
</div>
Húzási Korlátok: cdkDragBoundary
Megadhatjuk, hogy a húzható elem ne léphessen túl egy bizonyos konténeren. Ezt a cdkDragBoundary
direktíva segítségével tehetjük meg, amely egy CSS szelektort vár.
<div class="boundary-container">
<div class="box" cdkDrag [cdkDragBoundary]="'.boundary-container'">
Korlátolt mozgás
</div>
</div>
Gyakorlati Tippek és Bevált Gyakorlatok
- Stílusok Kezelése: Az Angular CDK szándékosan stílusmentes. Ez azt jelenti, hogy minden vizuális visszajelzést (mint például a
cdk-drag-preview
,cdk-drag-placeholder
,cdk-drop-list-dragging
,cdk-drop-list-receiving
osztályok) nekünk kell megírnunk a CSS-ben. Használjuk ki ezeket az osztályokat a professzionális felhasználói élmény érdekében. - Akadálymentesség (Accessibility): A CDK alapvetően támogatja az akadálymentességet (pl. billentyűzetről történő navigációt), de fontos, hogy mi magunk is odafigyeljünk rá. Használjunk releváns ARIA attribútumokat, és gondoskodjunk arról, hogy a billentyűzetes interakció is zökkenőmentes legyen.
- Teljesítmény: Nagy listák vagy komplex elemek esetén a drag-and-drop művelet befolyásolhatja a teljesítményt. Figyeljünk a Change Detection stratégiára (pl.
OnPush
), és optimalizáljuk az adatkezelést, ha szükséges. Kerüljük a feleslegesen sok újrarenderelést. - Felhasználói Visszajelzés: Mindig biztosítsunk világos vizuális visszajelzést a felhasználónak. Mutassuk, mi az, ami húzható, hová dobható, és mi történik az elemmel a művelet során. A
cursor: grab;
éscursor: grabbing;
CSS tulajdonságok, valamint a fent említett placeholder/preview testreszabás nagyszerűen szolgálják ezt a célt. - Adatmodellezés: Győződjünk meg róla, hogy az adatmodellünk konzisztens marad a DOM változásaival. A
moveItemInArray
éstransferArrayItem
segédfüggvények gondoskodnak erről az egyszerű tömbök esetén, de komplexebb adatszerkezeteknél nekünk kell figyelni az adatok integritására.
Összefoglalás
Az Angular CDK DragDropModule
egy rendkívül erőteljes és rugalmas eszköz a drag-and-drop funkcionalitás megvalósításához Angular alkalmazásokban. A könnyű telepítéstől az alapvető húzási és listarendezési képességeken át, egészen a listák közötti áthelyezésig és a fejlett testreszabási lehetőségekig, a CDK mindent megad, amire szükségünk lehet.
A stílusoktól való elvonatkoztatásnak köszönhetően teljes szabadságot élvezhetünk a design terén, miközben a CDK gondoskodik a mögöttes logikáról és az akadálymentességről. Reméljük, ez az útmutató segített megérteni, hogyan integrálhatja ezt az interaktív funkciót a saját projektjeibe, és hogyan emelheti a felhasználói élményt egy teljesen új szintre. Ne habozzon kísérletezni, és fedezze fel a CDK által kínált összes lehetőséget!
Leave a Reply