August 25, 2021 angular9 study

Posted by lucilue2003 on Sun, 19 Dec 2021 00:46:22 +0100

1. Build and run angular application

Explanation of official documents: Build and run angular applications

2.ChangeDetection

Function: detect the internal state of the program, and then reflect it on the UI.

Cause state changes: Events, XHR, Timers.
ApplicationRef listens to the onTurnDone of NgZone and performs detection.

Default policy

In angular, each component has a corresponding state change when rendering. Each component tree corresponds to a ChangeDetection tree. When the state of an angular component changes, the default policy of angular will change the entire ChangeDetection The (state) tree runs again to detect who has changed and where the change should be reflected in the UI. This default global detection strategy consumes performance in large projects.

OnPush policy

Therefore, the OnPush strategy is introduced here: where the state changes, it is detected. Where there is no state change, it will not be detected unless the detection mechanism is triggered manually, so as to avoid running the whole tree again.

Usage: in the corresponding of the component ts file import

import {ChangeDetectionStrategy} from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush // use
})

3. ChangeDetectorRef

When our components change, angular will perform change detection on our components. If it detects that our data has changed, it will perform some operations, such as updating the view when modifying the bound data. In this way, when we have more component data, many operations in angular will be carried out quietly. Therefore, ChangeDetectorRef is needed to detect the changes of data and update the view data in real time.

1. Introducing the ChangeDetectorRef module

import { ChangeDetectorRef } from "angular";

2. Statement

constructor(private cd:ChangeDetectorRef) {}

3. Use

this.cd.detectChanges(); // Real time detection of changes in pages and their sub elements

All detection methods of ChangeDetectorRef

  1. markForCheck(): when the input has changed or an event occurs in the view, Components are usually marked dirty (need to be re rendered). Calling this method ensures that the component is checked even if those triggers are not triggered. If the changeDetection: ChangeDetectionStrategy.OnPush condition is set in the component's metadata, change detection will not be performed again unless the method is called manually.

  2. detach(): detach the change detector from the change detection tree. The change detector of this component will no longer perform change detection unless the reattach() method is called manually.

  3. reattach(): re add the separated change detector so that the component and its subcomponents can perform change detection.

  4. detectChanges() -: perform a change detection from the component to each sub component to check the view and its sub views.

4. Attribute instruction

Renderer2 and ElementRef

Angular does not advocate direct operation of DOM. The operation of DOM should be carried out through Renderer2. ElementRef can be understood as a reference to a DOM element.

The following is a user-defined drag and drop instruction to understand the use and customization of attribute type instructions:
1. Create a new drag instruction file through ng g directive drag

import {Directive, ElementRef, HostListener, Input, Renderer2} from '@angular/core';
import {DragDropService} from "./drag-drop.service";

@Directive({
  selector: '[app-draggable]' //The default is appDrag. You can customize and modify the external instruction attribute name
})
export class DragDirective {
  private _isDraggable = false;

  /**
   * Here, use @ input ('app draggable ') to turn the app draggable instruction into an inputable instruction (use true/false to control whether the instruction works)
   * For example, when an element in an html page uses the attribute instruction, [app draggable] = "true",
   * It is equivalent to calling the set isDraggable(val: boolean) method, and the parameters received by the set method are the values set by the [app draggable] instruction
   * set/get Method is not required, but when you want to do additional operations, you can operate in the set/get method of the corresponding input attribute
   */
  @Input('app-draggable')
  set isDraggable(val: boolean) {
    this._isDraggable = val;
    // To make the element draggable, you must set a draggable attribute for the target element, and determine whether it can be dragged through the passed val
    this.rd.setAttribute(this.el.nativeElement, 'draggable', `${val}`)
  }

  get isDraggable() {
    return this._isDraggable;
  }

  // You can bind multiple additional input attributes to the attribute instruction, where a class name is bound
  @Input() draggedClass: string = '';
  @Input() dragTag: string = '';
  @Input() dragData: any;

  constructor(
    private el: ElementRef,
    private rd: Renderer2,
    private service: DragDropService,
  ) { }

  @HostListener('dragstart', ['$event'])
  onDragStart(ev: Event) {
    // Event is triggered for the current target element
    if (this.el.nativeElement === ev.target) {
      // Add a class to the target element
      this.rd.addClass(this.el.nativeElement, this.draggedClass);
      this.service.setDragData({tag: this.dragTag, data: this.dragData});
    }
  }

  @HostListener('dragend', ['$event'])
  onDragEnd(ev: Event) {
    if (this.el.nativeElement === ev.target) {
      // Remove the class of the target element
      this.rd.removeClass(this.el.nativeElement, this.draggedClass);
    }
  }

}

2. Create a new drag instruction file through ng g directive drop

import {Directive, ElementRef, HostListener, Input, Renderer2, Output, EventEmitter} from '@angular/core';
import {DragData, DragDropService} from "./drag-drop.service";
import {take} from "rxjs/operators";

@Directive({
  selector: '[app-droppable]'
})
export class DropDirective {
  @Output() dropped = new EventEmitter<DragData>();
  @Input() dragEnterClass: string = '';
  @Input() dropTags: string[] = [];
  private data$: any;

  constructor(
    private el: ElementRef,
    private rd: Renderer2,
    private service: DragDropService,
  ) {
    // The operator take(1) is added to prevent dragging from being triggered multiple times, causing the specified defined event to be triggered again after the dragData has been cleared, resulting in an error on the console
    this.data$ = this.service.getDragData().pipe(take(1));
  }

  @HostListener('dragenter', ['$event'])
  onDragEnter(ev: Event) {
    ev.preventDefault();
    ev.stopPropagation();
    if (this.el.nativeElement === ev.target) {
      this.data$.subscribe((dragData: DragData) => {
        if (this.dropTags.indexOf(dragData.tag) > -1) {
          this.rd.addClass(this.el.nativeElement, this.dragEnterClass);
        }
      });

    }
  }

  @HostListener('dragover', ['$event'])
  onDragOver(ev: Event) {
    ev.preventDefault();
    ev.stopPropagation();
    if (this.el.nativeElement === ev.target) {
      this.data$.subscribe((dragData: DragData) => {
        if (this.dropTags.indexOf(dragData.tag) > -1) {
          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'])
  onDragLeave(ev: Event) {
    ev.preventDefault();
    ev.stopPropagation();
    if (this.el.nativeElement === ev.target) {
      this.data$.subscribe((dragData: 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: DragData) => {
        if (this.dropTags.indexOf(dragData.tag) > -1) {
          this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);
          // this.dropped.emit(dragData);
          this.service.clearDragData();
        }
      });

    }
  }
}

3. html file and corresponding scss

<div class="wra">
  <div class="left" [app-draggable]="true" [dragTag]="'task-list'"
       [dragData]="[1,2,3,4,5]" [draggedClass]="'drag-start'"
       app-droppable [dragEnterClass]="'drag-enter'" [dropTags]="['task-item1', 'task-list1']">
    <div class="item" *ngFor="let item of [1, 2, 3, 4, 5]" [app-draggable]="true" [dragTag]="'task-item'"
         [dragData]="item" [draggedClass]="'drag-start'">
      This list item is {{item}}
    </div>
  </div>
  <div class="right" [app-draggable]="true" [dragTag]="'task-list1'"
       [dragData]="[6,7,8,9]" [draggedClass]="'drag-start'"
       app-droppable [dragEnterClass]="'drag-enter'" [dropTags]="['task-item', 'task-list']">
    <div class="item" *ngFor="let item of [6, 7, 8, 9]" [app-draggable]="true" [dragTag]="'task-item1'"
         [dragData]="item" [draggedClass]="'drag-start'">
      This list item is {{item}}
    </div>
  </div>
</div>

// ===========================
.wra {
  width: 100%;
  display: flex;
}

.left, .right {
  margin-left: 20px;
  border: 1px solid pink;
  padding: 10px;
  border-radius: 4px;
}

.item {
  line-height: 30px;
  width: 200px;
  padding: 0 10px;
  border: 1px solid green;
  margin: 10px 0;
  border-radius: 4px;
}

.drag-start {
  opacity: 0.5;
  border: 2px dashed #ff525b;
}

.drag-enter {
  background-color: rgba(0, 0, 0, 0.4);
}

4. The service file used in the command

import { Injectable } from '@angular/core';
import {BehaviorSubject, Observable} from "rxjs";

export interface DragData {
  tag: string; // Which element triggers the drag of the tag? Uniqueness
  data: any; // Transmitted data
}

@Injectable({
  providedIn: 'root'
})
export class DragDropService {

  private _dragData = new BehaviorSubject<DragData | null>(null);

  constructor() { }

  setDragData(data: DragData) {
    this._dragData.next(data);
  }

  getDragData(): Observable<DragData | null> {
    return this._dragData.asObservable();
  }

  clearDragData() {
    this._dragData.next(null);
  }
}

Topics: angular