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
-
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.
-
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.
-
reattach(): re add the separated change detector so that the component and its subcomponents can perform change detection.
-
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); } }