Angular 自定義拖拽指令

指令

組件是一種帶模版的指令。指令是超級。css

結構型指令(改變佈局)和屬性型指令(改變外觀和行爲)。數組

Renderer2和ElementRef數據結構

Angular不提倡直接操做DOMapp

對於DOM的操做應該經過Renderer2進行。ide

ElementRef是指向DOM元素的引用佈局

拖拽指令實例

一、新建directive module

$ ng g m directive
CREATE src/app/directive/directive.module.ts (193 bytes)

2, 在指令文件夾下的drag-drop文件夾裏新建一個drag和一個drop

$ ng g d directive/drag-drop/drag
$ ng g d directive/drag-drop/drop

3,改一下selector

selector: '[appDrag]'改成
selector: '[app-draggable]'
selector: '[appDrop]'改成
selector: '[app-droppable]'

4,在SharedModule中導入DirectiveModule

import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { MaterialModule } from "../material/material.module";
import { ConfirmDialogComponent } from "./confirm-dialog/confirm-dialog.component";
import { DirectiveModule } from '../directive/directive.module';

@NgModule({
  imports: [CommonModule, MaterialModule, DirectiveModule],
  exports: [CommonModule, MaterialModule, DirectiveModule],
  declarations: [ConfirmDialogComponent],
  entryComponents: [ConfirmDialogComponent]
})
export class SharedModule { }

5,把DirectiveModule中多餘的CommonDodule刪掉,而後把Drag和Drop兩個指令導出

import { NgModule } from '@angular/core';
import { DragDirective } from './drag-drop/drag.directive';
import { DropDirective } from './drag-drop/drop.directive';

@NgModule({
  declarations: [DragDirective, DropDirective],
  exports: [DragDirective, DropDirective],
})
export class DirectiveModule { }

 

6,drag指令

使用@HostListener監聽dragstart事件和dragend事件。this

使用ElementRef獲取元素。spa

使用Renderer2修改樣式。3d

draggedClass是一個輸入型參數。因此selector 爲selector: '[app-draggable][draggedClass]'。code

app-draggable設置爲true就能夠拖拽,爲false不可拖拽。

  private _isDraggble = false;

  set isDraggable(value){
    this._isDraggble=value;
  }
  get isDraggable(){
    return this._isDraggble;
  }

給set方法加上@Input(app-draggable)。 這樣在調用app-draggable= "true"的時候會調用set方法。

import { Directive, HostListener, Host, ElementRef, Renderer2, Input } from '@angular/core';

@Directive({
  selector: '[app-draggable][draggedClass]'
})
export class DragDirective {

  private _isDraggble = false;

  @Input('app-draggable')
  set isDraggable(value:boolean){
    this._isDraggble=value;
    this.rd.setAttribute(this.el.nativeElement,'draggable',`${value}`);
  }
  get isDraggable(){
    return this._isDraggble;
  }
  @Input()
  draggedClass:string;
  constructor(private el:ElementRef, private rd:Renderer2) { }

  @HostListener('dragstart', ['$event'])
  ondragstart(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.rd.addClass(this.el.nativeElement, this.draggedClass);//往el上增長一個class
    }

  }

  @HostListener('dragend', ['$event'])
  ondragend(ev:Event){
    if(this.el.nativeElement===ev.target){
      this.rd.removeClass(this.el.nativeElement, this.draggedClass);
    }
  }
}

在task-item組件中使用

//紅色dash虛線半透明
.drag-start
{ opacity: 0.5; border: #ff525b dashed 2px; }
<mat-list-item class="container" [@item]="widerPriority" [ngClass]="{
  'priority-normal':item.priority===3,
  'priority-important':item.priority===2,
  'priority-emergency':item.priority===1
}" [app-draggable]="true" [draggedClass]=" 'drag-start' "
  (click)="onItemClick()">
  ......
</mat-list-item>

7,drop指令

drop要監聽dragenter,dragover,dragleave,drop四個事件。

須要改變本身的css。即拖放區域的css。變暗。

import { Directive, HostListener, ElementRef, Renderer2, Input  } from '@angular/core';

@Directive({
  selector: '[app-droppable][dragEnterClass]'
})
export class DropDirective {

  @Input()
  dragEnterClass:string;
  constructor(private el:ElementRef, private rd:Renderer2) { }

  @HostListener('dragenter', ['$event'])
  onDragEnter(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
    }

  }

  @HostListener('dragover', ['$event'])
  onDragOver(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      
    }

  }

  @HostListener('dragleave', ['$event'])
  onDragLeave(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
    }

  }

  @HostListener('drop', ['$event'])
  onDrop(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
    }

  }
}
View Code

在task-home中使用drop指令

.drag-enter{
  background-color: dimgray;
}
  <app-task-list *ngFor="let list of lists" 
  class="list-container" app-droppable="true" [dragEnterClass]=" 'drag-enter' "
  >
    ......
  </app-task-list>

 至此,問題是不能多重拖拽(list-item和list都能拖拽,區分不開。)和攜帶數據。

八、解決多重拖拽和攜帶數據的問題

建立一個service

$ ng g s directive/drag-drop

在DirectiveModule中providers裏聲明一下

import { NgModule } from '@angular/core';
import { DragDirective } from './drag-drop/drag.directive';
import { DropDirective } from './drag-drop/drop.directive';
import { DragDropService } from './drag-drop.service';

@NgModule({
  declarations: [DragDirective, DropDirective],
  exports: [DragDirective, DropDirective],
  providers:[DragDropService]
})
export class DirectiveModule { }

drag-drop.service.ts

import { Injectable } from '@angular/core';
import { Observable,BehaviorSubject } from 'rxjs';

//數據結構
export interface DragData{
  tag:string; //多重拖拽的話是哪一級拖拽,用戶本身保證惟一性,不能重複
  data:any; //傳遞的數據
}


@Injectable({
  providedIn: 'root'
})
export class DragDropService {
  //用BehaviorSubject總能記住上一次的值
  private _dragData = new BehaviorSubject<DragData>(null);

  setDragData(data:DragData){
    this._dragData.next(data);
  }

  getDragData():Observable<DragData>{
    return this._dragData.asObservable();
  }

  clearDragData(){
    this._dragData.next(null);
  }
  constructor() { }
}

9,更新drag

在drag的時候須要多定義一個屬性dragTag,

constructor中注入新的DragDropService,

在開始拖拽的時候就給service set上數據,這裏須要多定義一個dragData。import { Directive, HostListener, Host, ElementRef, Renderer2, Input } from '@angular/core';import { DragDropService } from '../drag-drop.service';

 @Directive({ selector: '[app-draggable][dragTag][dragData][draggedClass]' }) export class DragDirective { private _isDraggble = false; @Input('app-draggable') set isDraggable(value:boolean){ this._isDraggble=value; this.rd.setAttribute(this.el.nativeElement,'draggable',`${value}`); } get isDraggable(){ return this._isDraggble; } @Input() draggedClass:string;
//多定義一個dragTag @Input() dragTag:string;
//給DragDropservice傳遞的數據 @Input() dragData:any constructor( private el:ElementRef, private rd:Renderer2,
//注入DragDropService private service:DragDropService) { } @HostListener(
'dragstart', ['$event']) ondragstart(ev:Event){ //判斷drag元素是否是指令應用的元素髮起的 if(this.el.nativeElement===ev.target){ this.rd.addClass(this.el.nativeElement, this.draggedClass);//往el上增長一個class
//進入時候給service傳遞上數據
this.service.setDragData({tag:this.dragTag,data:this.dragData}); } } @HostListener('dragend', ['$event']) ondragend(ev:Event){ if(this.el.nativeElement===ev.target){ this.rd.removeClass(this.el.nativeElement, this.draggedClass); } } }

 

10,更新drop

對於drop來說,它的tags是一個數組,而不是字符串了。

由於拖放放的區域,可能會支持多個拖的區域

因此放的時候原來的判斷都有問題,首先須要判斷這個拖拽是否是你可以接收的。

創建一個私有的data$,在constructor裏訂閱data。

drop指令還須要一個Output,由於須要何時drop。

import { Directive, HostListener, ElementRef, Renderer2, Input, Output, EventEmitter  } from '@angular/core';
import { DragDropService, DragData } from '../drag-drop.service';
import { take } from 'rxjs/operators';

@Directive({
  selector: '[app-droppable][dropTags][dragEnterClass]'
})
export class DropDirective {

  @Output()
  dropped = new EventEmitter<DragData>();
  @Input()
  dragEnterClass:string;
  @Input() dropTags:string[] = [];
  private data$;

  constructor(
    private el:ElementRef, 
    private rd:Renderer2,
    private service:DragDropService) {
      this.data$ = this.service.getDragData().pipe(
        take(1));
     }

  // @HostListener('dragenter', ['$event'])
  // onDragEnter(ev:Event){
  //   //判斷drag元素是否是指令應用的元素髮起的
  //   if(this.el.nativeElement===ev.target){
  //     this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
  //   }
  // }
  @HostListener('dragenter', ['$event'])
  onDragEnter(ev:Event){
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.data$.subscribe(dragData=>{
        if(this.dropTags.indexOf(dragData.tag)>-1){
          this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
        }
      });
    }
  }


  // @HostListener('dragover', ['$event'])
  // onDragOver(ev:Event){
  //   //判斷drag元素是否是指令應用的元素髮起的
  //   if(this.el.nativeElement===ev.target){ 
  //   }
  // }
  //dragover容許進行data transfer的一些特效
  @HostListener('dragover', ['$event'])
  onDragOver(ev:Event){
    //須要支持多級拖拽,因此要防止事件冒泡
    ev.preventDefault(); 
    ev.stopPropagation();
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){ 
      this.data$.subscribe(dragData=>{
        if(this.dropTags.indexOf(dragData.tag)>-1){
          this.rd.setProperty(ev,'dataTransfer.effectAllowed','all');
          this.rd.setProperty(ev,'dataTransfer.fropEffect','move');
        }else{
          this.rd.setProperty(ev,'dataTransfer.effectAllowed','none');
          this.rd.setProperty(ev,'dataTransfer.dropEffect','none');
        }
      });
    }
  }

  @HostListener('dragleave', ['$event'])
  onDragLeave(ev:Event){
    ev.preventDefault();
    ev.stopPropagation();
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.data$.subscribe(dragData=>{
        if(this.dropTags.indexOf(dragData.tag)>-1){
          this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
        }
      });
    }

  }

  @HostListener('drop', ['$event'])
  onDrop(ev:Event){
    ev.preventDefault();
    ev.stopPropagation();
    //判斷drag元素是否是指令應用的元素髮起的
    if(this.el.nativeElement===ev.target){
      this.data$.subscribe(dragData => {
        if(this.dropTags.indexOf(dragData.tag)>-1){
          this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增長一個class
          this.dropped.emit(dragData);//drop的時候把dragData發射出去
          this.service.clearDragData(); //drop的時候把data clear掉,不然會影響下一次拖拽
        }
      });
    }

  }
}

 

 

11,改造模版調用

對於taskItem

<mat-list-item class="container" [@item]="widerPriority" [ngClass]="{
  'priority-normal':item.priority===3,
  'priority-important':item.priority===2,
  'priority-emergency':item.priority===1
}"
  [app-draggable]= "true" [dragTag]= "'task-item'"
  [draggedClass]=" 'drag-start' " [dragData]="item"
  (click)= "onItemClick()">

對於taskHome

既能drag又能drop

此外還要處理一個dropped事件

<div class="task-list">
  <app-task-list *ngFor="let list of lists" 
  class="list-container"
  app-droppable="true"
  [dropTags]="['task-item','task-list']"
  [dragEnterClass]=" 'drag-enter' "
  [app-draggable]="true" [dragTag]=" 'task-list' "
  [draggedClass]=" 'drag-start' "
  [dragData]="list" (dropped)="handleMove($event,list)"
  >
  handleMove(srcData,List){
    switch (srcData.tag) {
      case 'task-item':
        console.log('handling item');
        break;
      case 'task-list':
        console.log('handling list');
      default:
        break;
    }
  }

 最終效果:

相關文章
相關標籤/搜索