官方文档讲解:构建并运行angular应用
作用:检测程序内部状态,然后反映到UI上。
引起状态变化:Events
(事件)、XHR
(异步请求)、Timers
(定时器)。
ApplicationRef
监听NgZone
的onTurnDone
,然后执行检测。
在angular中,每个组件渲染的时候都有对应的状态变化。每个组件树对应着一个ChangeDetection(状态)树
。当angular组件发生状态变化时,angular的默认策略会把整个ChangeDetection(状态)树
跑一遍,去检测到底谁发生了变化,并且应该把这个变化反映到UI的哪个地方。这种默认的全局检测策略,在大型的项目中是很消耗性能的。
因此这里引入OnPush策略
:哪里状态发生了变化才检测哪里,其他没有发生状态变化的地方,除非手动触发检测机制,否则不会进行检测,避免了把整个树跑一遍。
使用方式:在组件对应的.ts
文件引入
import {ChangeDetectionStrategy} from '@angular/core';
@Component({
selector: 'app-hello',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush // 使用
})
angular 会在我们的组件发生变化的时候,对我们的组件执行变化检测,如果检测到我们的数据发生了变化,就会执行某些操作,如修改绑定数据的时候更新视图。这样一来,当我们的组件数据比较多的时候,angular就会有很多操作在静悄悄地进行,因此,就需要ChangeDetectorRef
来实时检测数据的变化并更新视图数据。
1、 引入ChangeDetectorRef
模块
import { ChangeDetectorRef } from “angular”;
2、声明
constructor(private cd:ChangeDetectorRef) {}
3、使用
this.cd.detectChanges(); // 实时检测页面及其子元素的变化
markForCheck()
: 当输入已更改或视图中发生了事件时,组件通常会标记为脏的(需要重新渲染)。调用此方法会确保即使那些触发器没有被触发,也仍然检查该组件。在组件的 metadata 中如果设置了 changeDetection: ChangeDetectionStrategy.OnPush
条件,那么变化检测不会再次执行,除非手动调用该方法。
detach()
: 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach()
方法。
reattach()
: 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测。
detectChanges()
-:从该组件到各个子组件执行一次变化检测,检查该视图及其子视图。
Angular不提倡直接操作DOM,对于DOM的操作应该通过Renderer2
来进行。ElementRef
可以理解成指向DOM元素的引用。
以下通过一个自定义拖拽指令来理解属性型指令的使用及自定义:
1、通过ng g directive drag
新建一个拖的指令文件
import {Directive, ElementRef, HostListener, Input, Renderer2} from '@angular/core';
import {DragDropService} from "./drag-drop.service";
@Directive({
selector: '[app-draggable]' //默认为appDrag,可自定义修改对外的指令属性名称
})
export class DragDirective {
private _isDraggable = false;
/**
* 这里通过 @Input('app-draggable') 让 app-draggable 指令也变为一个可输入的指令(通过true/false来控制指令是否起作用)
* 例如,当html页面里的元素使用该属性指令时,[app-draggable] = "true",
* 相当于调用了 set isDraggable(val: boolean) 方法,set方法接收的参数就为[app-draggable]指令设置的值
* set/get 方法不是必须的,只是当你要做额外的操作时,可以在对应的输入属性的set/get 方法中进行操作
*/
@Input('app-draggable')
set isDraggable(val: boolean) {
this._isDraggable = val;
// 要让元素可拖拽,必须给目标元素设置一个 可拖拽 的属性,通过传入的 val 决定是否可拖拽
this.rd.setAttribute(this.el.nativeElement, 'draggable', `${val}`)
}
get isDraggable() {
return this._isDraggable;
}
// 可以给属性指令绑定多个额外的输入属性,这里绑定了一个class名称
@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) {
// 事件为当前的目标元素触发
if (this.el.nativeElement === ev.target) {
// 给目标元素添加class
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) {
// 移除目标元素的class
this.rd.removeClass(this.el.nativeElement, this.draggedClass);
}
}
}
2、通过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,
) {
// 加上操作符 take(1),防止拖拽被触发多次,导致dragData已经清空后再次触发指定定义的事件导致控制台报错
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
文件及对应的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、指令里面使用到的service
文件
import { Injectable } from '@angular/core';
import {BehaviorSubject, Observable} from "rxjs";
export interface DragData {
tag: string; // 标记是哪个元素触发的拖拽,唯一性
data: any; // 传递的数据
}
@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);
}
}