Angular属性型指令

赵修诚
2023-12-01

注:代码已上传到github,仅作为demo进行演示并学习,没有进行进一步开发,如有问题请随时联系我

github demo地址:https://github.com/Mr-WangZhe/DirectiveDemo

一.指令

1.什么是指令

  • 组件是一种自带模板的指令
  • 结构型(Structural)指令和属性型(Attribute)指令

2.Renderer2和ElementRef

  • Angular不提倡直接操作DOM
  • 对于DOM的操作应该通过Renderer2来进行
  • ElementRef可以理解成指向DOM元素的引用

二.拖拽指令实例代码

  • 创建directive的moduleng g m directive

  • 在module下,创建drag的directiveng g d directive/drag --spec=false

  • 在module下,创建drop的directiveng 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);
        }
      }
      
 类似资料: