前言
寫到表單功能的時候,就考慮如果表單能夠拖動式地新增,無疑能夠加強使用者的使用體驗感。所以就嘗試著實現拖動功能。如下圖,能夠將一個表單拖動到另一個表單。
環境配置
使用到了angular的cdk:@angular/cdk/drag-drop
Angualr drag-drop裡面的功能能讓我們非常方便的處理頁面上檢視的拖拽(自由拖拽、列表排序拖拽、列表之間拖拽)問題。
官網:https://material.angular.io/c...
cdk是Angular Material 下的一個模組.我們安裝一下Material。
安裝material
ng add @angular/material
ng add命令將安裝 Angular Material、 元件開發工具包 (CDK)
Module匯入
import { DragDropModule } from '@angular/cdk/drag-drop';
imports: [
...
DragDropModule
]
功能實現
講一下最常用的幾種用法
拖拽
html程式碼:
最主要的就是加上了 cdkDrag
<div cdkDrag> drag me</div>
效果:
排序
使用cdkDropList,它新增在一組元素新增cdkDrag可拖動元素的集合外面。隨著元素的移動,專案將自動重新排列。
html:
<div class="list-group" cdkDropList
(cdkDropListDropped)="drop($event)">
<div class="list-group-item row" *ngFor="let customer of customers" cdkDrag>
{{customer.name}}
</div>
</div>
ts:
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.customers, event.previousIndex, event.currentIndex);
}
效果圖:
當然還可以橫向排序,只需要新增cdkDropListOrientation="horizontal"
<div class="box-list-horizontal" cdkDropList
cdkDropListOrientation="horizontal">
新增動畫
看起來上面的效果圖有些不太好看,我們可以給它新增一點動畫。
css:
// 拖拽時顯示的佔位符元素,而不是實際的元素
.cdk-drag-placeholder {
opacity: 0;
}
// 從動畫的位置到最終把它放在列表的位置上時的動畫
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
// 拖動元素時,看到的預覽元素
.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);
}
// 拖動元素時,其他元素改變位置看到的動畫
.list-group.cdk-drop-list-dragging .list-group-item:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
效果圖:
還有其他的其他的一些功能將不再一一講述,比如拖放位置鎖定、設定拖放邊界、拖放禁用功能。
可以看看官網:https://material.angular.io/c...
兩個列表之間的拖拽
目前需要使用的是列表之間的拖拽,所以重點講一下這個。
列表之間的連線
使用cdkDropListConnectedTo和id,可以連線兩個表,使它們之間能夠拖動自己的元素到另一個表中。
<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
也可以使用cdkDropListGroup。cdkDropListGroup在下的所有表都將自動連線到所有其他列表。我採用的是這種。
<div cdkDropListGroup>
<!-- All lists in here will be connected. -->
<div cdkDropList *ngFor="let list of lists"></div>
</div>
根據需求寫程式碼
首先,定義了一個表,需求是:不能排序,另一個不能拖動元素到此表單
<div class="row">
<div class="col-2">
<h2>可用表單</h2>
<div
cdkDropList
[cdkDropListData]="availableItems" // 拖動時所帶的元素
cdkDropListSortingDisabled // 禁用排序
[cdkDropListEnterPredicate]= "noReturnPredicate" // 定義了一個函式,返回false, 表示別的表單不能拖動元素到此表單
class="list-group">
<div class="list-group-item" *ngFor="let item of availableItems" cdkDrag>
<label> {{item.content}}</label>
</div>
</div>
</div>
</div>
定義了另一個表,需求是:可以排序,另一個表的元素拖動過來的時候,原來的元素不會消失。
<div class="col-6">
<h2>目前表單</h2>
<div
cdkDropList
class="list-group"
[cdkDropListData]="nowItem"
(cdkDropListDropped)="dropNowList($event)">
<div class="list-group-item row" cdkDrag *ngFor="let item of nowItem">
{{item.content}}
</div>
</div>
</div>
效果圖:
ts:
當元素從左邊的表拖動到右邊的表的時候,或者右邊的表排序的時候,右邊的表會執行dropNowList()函式。函式中進行了判斷,如程式碼中註釋所示
/**
* 現有表單
* @param event
*/
dropNowList(event: CdkDragDrop<FormItem[], any>) {
// 如果前容器等於現容器,說明是在排序,交換元素的位置
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
// 如果不同,說明是從另一個表過來的元素,獲取元素前容器的資料,複製元素,插入到本表中
// 如果使用transferItem(),則源元素會消失
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex)
}
}
遇到的問題
動態渲染
其實最開始想的是,元素一拖過去就動態渲染。但是,動態元件是一個整體,我們並不能拖動其中的某一個元件,只能拖動整體。如下效果圖:
所以最後放棄了動態渲染,讓使用者拖動好了表單再點選預覽按鈕。
拖動效果
從一個表拖動元素到另一個表到時候,這個元素在源表中會消失,雖然拖動過去之後會回來,但是看起來邏輯並不好。
示例:
所以需要實現的是,拖動元素的時候,這個元素在源表單不動。
但似乎DragModule並沒有屬性或者函式可以讓我們呼叫來解決這個問題。
在github有一場對這個問題的大型討論:
https://github.com/angular/co...
但似乎實現起來有點麻煩。
這個問題目前還不影響功能的實現,待後期有時間的時候再解決。