指令
組件是一種帶模版的指令。指令是超級。
結構型指令(改變布局)和屬性型指令(改變外觀和行為)。
Renderer2和ElementRef
Angular不提倡直接操作DOM
對於DOM的操作應該通過Renderer2進行。
ElementRef是指向DOM元素的引用
拖拽指令實例
1、新建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
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
}
}
}
在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
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; } }
最終效果:


