注:代码已上传到github,仅作为demo进行演示并学习,没有进行进一步开发,如有问题请随时联系我
github demo地址:https://github.com/Mr-WangZhe/DirectiveDemo
一.指令
1.什么是指令
- 组件是一种自带模板的指令
- 结构型(Structural)指令和属性型(Attribute)指令
2.Renderer2和ElementRef
- Angular不提倡直接操作DOM
- 对于DOM的操作应该通过Renderer2来进行
- ElementRef可以理解成指向DOM元素的引用
二.拖拽指令实例代码
-
创建directive的module
ng g m directive
-
在module下,创建drag的directive
ng g d directive/drag --spec=false
-
在module下,创建drop的directive
ng g d directive/drop --spec=false
-
将drag.directive.ts和drop.directive.ts修改指令名称为
[app-draggable]
和[app-droppable]
-
编写drag实现拖的目的
-
拖拽指令文件drag.directive.ts实例代码
import { Directive, HostListener, Input, ElementRef, Renderer2 } from '@angular/core'; import { DragDropService } from './drag-drop.service'; @Directive({ selector: '[app-draggable][dragTag][dragData][draggedClass]' }) export class DragDirective { private _isDraggable = false;//设置是否可拖拽的变量值 //定义属性方法(set) 使用的时候可以直接写成this.isDraggable=xxx;就会直接调用set方法 @Input('app-draggable')//定义app-draggable=xxx时就会调用set方法 set isDraggable(val) { this._isDraggable = val; this.rd.setAttribute(this.el.nativeElement,'draggable',`${val}`);//根据html规范,如果元素可拖拽或是不可拖拽,需要设置draggable属性值 } //定义属性方法(get) get isDraggable() { return this._isDraggable; } @Input() dragTag:string;//tag的唯一标识 @Input() dragData:any; @Input() draggedClass: string;//表示动态添加的样式 constructor( private el:ElementRef, private rd:Renderer2, private service: DragDropService) {} //通过event的target判断是否是指令应用的元素发起的 @HostListener('dragstart',['$event'])//监听拖开始的事件 onDragStart(ev:Event) { if(this.el.nativeElement === ev.target) { this.rd.addClass(this.el.nativeElement, this.draggedClass)//表示往el节点上应用draggedClass样式 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)//表示拖拽完成之后把样式移除 } } }
-
directive.module.ts文件定义
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DragDirective } from './drag.directive'; import { DropDirective } from './drop.directive'; import { DragDropService } from './drag-drop.service'; @NgModule({ imports: [ CommonModule ], declarations: [ DragDirective, DropDirective ], exports: [ DragDirective, DropDirective ] }) export class DirectiveModule { }
-
left-div.component.css定义拖拽时的css
.item-drag { border: coral dashed 3px; opacity: 0.5; }
-
left-div.component.html定义显示的html
<div class="leftDivBackground"> <div *ngFor="let item of items; let i = index" class="item" [app-draggable]="true" [draggedClass]="'item-drag'" [dragTag]="'div-item'" [dragData]="item"> {{item.value}} </div> </div>
-
-
编写drop实现放的目的
-
放置指令文件drop.directive.ts实例代码
import { Directive, HostListener, ElementRef, Renderer2, Input, Output, EventEmitter } from '@angular/core'; import { DragData, DragDropService } from './drag-drop.service'; import { take } from 'rxjs/operators'; @Directive({ selector: '[app-droppable][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));//订阅service,需要导入take,take是rxjs的操作符 } //通过event的target判断是否是指令应用的元素发起的 @HostListener('dragenter',['$event'])//drag的对象进入我的领域了 onDragEnter(ev:Event) { // 多种元素都可以拖拽的话,拖拽的时候可能会影响多个,所以需要防止事件传播 ev.preventDefault(); ev.stopPropagation(); if(this.el.nativeElement === ev.target) { this.data$.subscribe(dragData => {//取到data的时候,查看放的区域是否含有拖的tag if(this.dropTags.indexOf(dragData.tag) > -1) { this.rd.addClass(this.el.nativeElement, this.dragEnterClass)//表示往el节点上应用draggedClass样式 } }); } } @HostListener('dragover',['$event'])//drag的对象在我的上面 onDragOver(ev:Event) { ev.preventDefault(); ev.stopPropagation(); if(this.el.nativeElement === ev.target) { this.data$.subscribe(dragData => { if(this.dropTags.indexOf(dragData.tag) > -1) { //设置data transfer的特效 this.rd.setProperty(ev, 'dataTransfer.effectAllowed','all'); this.rd.setProperty(ev, 'dataTransfer.dropEffect','move'); }else{ this.rd.setProperty(ev, 'dataTransfer.effectAllowed','none'); this.rd.setProperty(ev, 'dataTransfer.dropEffect','none'); } }) } } @HostListener('dragleave',['$event'])//drag的对象离开我的领域 onDragLeave(ev:Event) { ev.preventDefault(); ev.stopPropagation(); 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) } }); } } @HostListener('drop',['$event'])//监听放的事件 onDrop(ev:Event) { ev.preventDefault(); ev.stopPropagation(); 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); this.dropped.emit(dragData);// 把dropData发射出去 this.service.clearDragData();//在放下的时候执行service的clear操作,否则会影响下一次的拖拽 } }); } } }
-
right-div.component.css文件定义拖拽悬浮的样式
.drag-enter { background-color: dimgray; }
-
right-div.component.html文件定义拖拽悬浮的html
<div class="rightDivBackground" app-droppable [dropTags]="['div-item','div-list']" [dragEnterClass]="'drag-enter'" [app-draggable]="true" [dragTag]="'div-list'" [draggedClass]="'drag-start'" [dragData]="items" (dropped)="handleMove($event)" > <div *ngFor="let item of items; let i = index" class="item">{{item.value}}</div> </div> <!-- 此元素既有拖也有放 -->
-
创建一个drag-drop service
ng g s directive/drag-drop
,并添加到privides中 -
drag-drop.service.ts定义拖放时的服务service,用于存放拖放元素的信息
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; export interface DragData { tag: string;//标记是哪个拖拽(在多级拖拽中),用户自定义 data: any;//表示传递的内容 } @Injectable() 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); } }
-
在app.component中存放元素的信息并进行数据的变更,对item的内容进行处理
- app.component.html
<div class="row"> <div class="col-6"> <app-left-div [items]="list1"> </app-left-div> </div> <div class="col-6"> <app-right-div [items]="list2" (changeItem)="handleResult($event)"> </app-right-div> </div> </div>
- app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Action'; list1 = [ {key:1,value:'item1'}, {key:2,value:'item2'}, {key:3,value:'item3'} ]; list2 = [ {key:4,value:'item4'}, {key:5,value:'item5'}, {key:6,value:'item6'} ]; handleResult(item) { this.list1 = this.list1.filter((listItem)=>{ if(item.key === listItem.key){ return false; }else{ return true; } }); this.list2.push(item); } }
-