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事件。
使用ElementRef获取元素。
使用Renderer2修改样式。
draggedClass是一个输入型参数。所以selector 为selector: '[app-draggable][draggedClass]'。
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都能拖拽,区分不开。)和携带数据。
8、解决多重拖拽和携带数据的问题
创建一个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
BehaviorSubjectBehaviorSubject总能记住上一次的最新值
在拖的时候把最新值next出去,给流里面发射一个新值。拽的时候,subject的时候会取到最新的值。
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: DragData, list: any) {
switch (srcData.tag) {
case 'task-item':
console.log('handling item');
break;
case 'task-list':
console.log('handling list');
break;
default:
break;
最终效果: