指令

组件是一种带模版的指令。指令是超级。

结构型指令(改变布局)和属性型指令(改变外观和行为)。

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

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
} }
}

在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;
}
}

最终效果:

Angular 自定义拖拽指令的更多相关文章

  1. vue 自定义拖拽指令

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 使dialog可拖拽指令

    在项目开发过程中,需要支持dialog弹窗可拖拽,则需要对dialog添加指令.具体操作说明如下: (1)在用于存放指令的文件夹内,新建拖拽指令文件夹,例如命名为:el-dragDialog,如下所示 ...

  3. (原创)[C#] 一步一步自定义拖拽(Drag&Drop)时的鼠标效果:(一)基本原理及基本实现

    一.前言 拖拽(Drag&Drop),属于是极其常用的基础功能. 无论是在系统上.应用上.还是在网页上,拖拽随处可见.同时拖拽时的鼠标效果也很漂亮,像这样: 这样: 还有这样: 等等等等. 这 ...

  4. 基于vite3+tauri模拟QQ登录切换窗体|Tauri自定义拖拽|最小/大/关闭

    前两天有给大家分享tauri+vue3快速搭建项目.封装桌面端多开窗口.今天继续来分享tauri创建启动窗口.登录窗口切换到主窗口及自定义拖拽区域的一些知识.希望对想要学习或正在学习的小伙伴有些帮助. ...

  5. Angular 元素拖拽

    拖动元素到指定区域 拖放的同时传递数据 1. 安装 ng2-drag-drop npm install ng2-drag-drop --save 2. 模板中配置可拖拽元素 // drag.compo ...

  6. 使div弹窗可拖拽指令

    在项目开发过程中,有些情况dialog弹窗,直接使用div模拟弹窗效果,并需要支持div可拖拽. div模拟弹窗效果: (1)在用于存放指令的文件夹内,新建js文件,命名为:drag.js.具体代码如 ...

  7. elementUI 弹出框添加可自定义拖拽和拉伸功能,并处理边界问题

    开发完后台管理系统的弹出框模块,被添加拖拽和拉伸功能,看了很多网上成熟的帖子引到项目里总有一点问题,下面是根据自己的需求实现的步骤: 首先在vue项目中创建一个js文件eg:dialog.js imp ...

  8. Angular17 Angular自定义指令

    1 什么是HTML HTML文档就是一个纯文本文件,该文件包含了HTML元素.CSS样式以及JavaScript代码:HTML元素是由标签呈现,浏览器会为每个标签创建带有属性的DOM对象,浏览器通过渲 ...

  9. 干货之UICollectionViewFlowLayout自定义排序和拖拽手势

    使用UICollectionView,需要使用UICollectionViewLayout控制UICollectionViewCell布局,虽然UICollectionViewLayout提供了高度自 ...

随机推荐

  1. 【linux】工作中linux系统常用命令操作整理

    1.Linux如何查看端口 使用lsof(list open files)命令,lsof -i:端口号 用于查看某一端口的占用情况,比如查看8000端口使用情况,lsof -i:8000. 或者使用n ...

  2. CF271D 【Good Substrings】

    定义哈希函数 \(H(c)=\sum_{i = 1} ^ m c_i*b^{m-i}\) \(H(C,K+1)=H(C,K)*b+C_{K+1}\)(K个坏字母) 用long long #includ ...

  3. TODO springboot学习笔记

    学习中,是在是搞不懂是什么狗屎....

  4. java频繁new对象的优化方案

    在实际开发中,某些情况下,我们可能需要频繁去创建一些对象(new),下面介绍一种,我从书上看到的,可以提高效率的方法. 首先,对于将会频繁创建的对象,我们要让这个类实现Cloneable接口,因为这个 ...

  5. linxu上安装mongodb3.6实战

    根据linux 版本到官网下载对应mongodb版本 查看服务器版本:cat /proc/version 查看linux发行版本:cat /etc/redhat-release 我用的阿里云服务器,对 ...

  6. 【bzoj 4449】[Neerc2015]Distance on Triangulation

    Description 给定一个凸n边形,以及它的三角剖分.再给定q个询问,每个询问是一对凸多边行上的顶点(a,b),问点a最少经过多少条边(可以是多边形上的边,也可以是剖分上的边)可以到达点b. I ...

  7. 第四周WordCount优化

    一.GitHub地址 https://github.com/kawoyi/Advanced-WordCounter最终由组长整合的组长github 二.psp表格 三.个人模块及实现 我负责的是输入模 ...

  8. Repeater取不到服务端控件

    <td>      <asp:Button ID="Button1" runat="server" Text="查看" O ...

  9. poj2689 Prime Distance题解报告

    题目戳这里 [题目大意] 给定一个区间[L,R],求区间内的质数相邻两个距离最大和最小的. [思路分析] 其实很简单呀,很明显可以看出来是数论题,有关于质数的知识. 要注意一下的就是L和R的数据范围都 ...

  10. selenium中的下拉框处理模块Select

    在UI自动化测试过程中,经常会遇到一些下拉框,如果我们基于Webdriver操作的话就需要click两次,而且很容易出现问题,实际上Selenium给我们提供了专门的Select(下拉框处理模块). ...