Angular 2 不触发变化检测的点击事件回调
Angular 2 click event callback without triggering change detection
我在尝试在 Angular 2 中的 (click)
事件回调函数中执行某些逻辑时遇到了重大问题,而不触发更改检测。
为什么我不想触发变化检测
回调函数执行简单的动画滚动到顶部。它不影响其他组件,因此我不需要更改检测来触发每个组件,事实上我真的不希望它触发!由于变化检测确实在每个组件中触发,因此移动设备上的动画性能极差。
我试过的
我知道我可以 运行 使用
在区域外编码
this._zone.runOutsideAngular(() => {
someFunction();
})
当您想要 运行 区域外的代码未被 click
、keyup
、keydown
事件等调用时,这会很好地工作
因为我的组件看起来像...
<button (click)="doThing()">Do that thing outside the zone!</button>
doThing() {
this._zone.runOutsideAngular(() => {
myFunction();
})
}
...myFunction
将 运行 在区域之外,但 click
事件将触发变化检测。
我已经在尽可能多的组件中实施了 OnPush
变更检测策略。
这里不触发变更检测很重要,但我找不到阻止它的方法。
编辑
请参阅此 plnk,所有组件中的更改检测都设置为 OnPush
。单击子行组件按钮会触发子行、行和应用程序组件中的 CD。
如果你使用 OnPush
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
})
那么更改检测应该仅限于发生点击事件的组件。
您可以分离 ChangeDetectorRef
constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {
ref.detach();
}
在重新附加此组件之前,不会 运行 更改检测。
我可以展示一个完整的 hack,但它似乎有效。
子行-component.ts
constructor(private zone: NgZone, private app: ApplicationRef) {}
rowClick() {
console.log('rowClick');
var a = 4;
let originalTick = (<any>this.app).constructor.prototype.tick;
(<any>this.app).constructor.prototype.tick = () => console.info('tick tick tick :)');
const subscriber = this.zone.onMicrotaskEmpty.subscribe({
next: () => {
console.info('tick has been stopped. So we can return everything in its place');
subscriber.unsubscribe();
(<any>this.app).constructor.prototype.tick = originalTick;
}});
}
如前所述,即使将发生 click
事件的组件设置为 OnPush
仍会导致触发更改检测。
开箱即用的事件处理
@Component({
selector: 'test'
template: `<button (click)="onClick($event)">Click me</button>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
onClick(e:Event) {
e.preventDefault();
e.stopPropagation();
this._zone.runOutsideAngular(() => {
// gets run outside of Angular but parent function still triggers change detection
});
return false;
/*
* change detection always triggered
* regardless of what happens above
*/
}
}
大多数时候这可能不是问题,但是当某些功能的渲染和绘画性能至关重要时,我采用完全绕过 Angular 的内置事件处理的方法。根据我自己的实验,以下似乎是解决此问题的最简单和最可维护的方法。
绕过开箱即用的事件处理
这里的想法是使用 RxJS 手动绑定到我想要的任何事件,在本例中是单击,使用 fromEvent
。此示例还演示了清理事件订阅的做法。
import { Component, ElementRef, OnInit, OnDestroy, NgZone } from '@angular/core';
import { takeUntil } from "rxjs/operators";
import { Subscription, fromEvent, Subject } from 'rxjs';
@Component({
selector: 'test'
template: `<button #myButton>Click me</button>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent implements OnInit {
private _destroyed$: Subject<null> = new Subject();
// reference to template variable
@ViewChild('myButton') myButton: ElementRef<HTMLButtonElement>;
constructor(private _zone: NgZone){}
ngOnInit() {
this.subscribeToMyButton();
}
subscribeToMyButton() {
this._zone.runOutsideAngular(() => {
fromEvent(this.myButton.nativeElement, 'click')
.pipe(takeUntil(this._destroyed$))
.subscribe(e => {
console.log('Yayyyyy! No change detection!');
});
});
}
ngOnDestroy() {
// clean up subscription
this._destroyed$.next();
this._destroyed$.complete();
}
}
希望这对处于类似情况的其他人有所帮助。
我在尝试在 Angular 2 中的 (click)
事件回调函数中执行某些逻辑时遇到了重大问题,而不触发更改检测。
为什么我不想触发变化检测
回调函数执行简单的动画滚动到顶部。它不影响其他组件,因此我不需要更改检测来触发每个组件,事实上我真的不希望它触发!由于变化检测确实在每个组件中触发,因此移动设备上的动画性能极差。
我试过的
我知道我可以 运行 使用
在区域外编码this._zone.runOutsideAngular(() => {
someFunction();
})
当您想要 运行 区域外的代码未被 click
、keyup
、keydown
事件等调用时,这会很好地工作
因为我的组件看起来像...
<button (click)="doThing()">Do that thing outside the zone!</button>
doThing() {
this._zone.runOutsideAngular(() => {
myFunction();
})
}
...myFunction
将 运行 在区域之外,但 click
事件将触发变化检测。
我已经在尽可能多的组件中实施了 OnPush
变更检测策略。
这里不触发变更检测很重要,但我找不到阻止它的方法。
编辑
请参阅此 plnk,所有组件中的更改检测都设置为 OnPush
。单击子行组件按钮会触发子行、行和应用程序组件中的 CD。
如果你使用 OnPush
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
})
那么更改检测应该仅限于发生点击事件的组件。
您可以分离 ChangeDetectorRef
constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {
ref.detach();
}
在重新附加此组件之前,不会 运行 更改检测。
我可以展示一个完整的 hack,但它似乎有效。
子行-component.ts
constructor(private zone: NgZone, private app: ApplicationRef) {}
rowClick() {
console.log('rowClick');
var a = 4;
let originalTick = (<any>this.app).constructor.prototype.tick;
(<any>this.app).constructor.prototype.tick = () => console.info('tick tick tick :)');
const subscriber = this.zone.onMicrotaskEmpty.subscribe({
next: () => {
console.info('tick has been stopped. So we can return everything in its place');
subscriber.unsubscribe();
(<any>this.app).constructor.prototype.tick = originalTick;
}});
}
如前所述,即使将发生 click
事件的组件设置为 OnPush
仍会导致触发更改检测。
开箱即用的事件处理
@Component({
selector: 'test'
template: `<button (click)="onClick($event)">Click me</button>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
onClick(e:Event) {
e.preventDefault();
e.stopPropagation();
this._zone.runOutsideAngular(() => {
// gets run outside of Angular but parent function still triggers change detection
});
return false;
/*
* change detection always triggered
* regardless of what happens above
*/
}
}
大多数时候这可能不是问题,但是当某些功能的渲染和绘画性能至关重要时,我采用完全绕过 Angular 的内置事件处理的方法。根据我自己的实验,以下似乎是解决此问题的最简单和最可维护的方法。
绕过开箱即用的事件处理
这里的想法是使用 RxJS 手动绑定到我想要的任何事件,在本例中是单击,使用 fromEvent
。此示例还演示了清理事件订阅的做法。
import { Component, ElementRef, OnInit, OnDestroy, NgZone } from '@angular/core';
import { takeUntil } from "rxjs/operators";
import { Subscription, fromEvent, Subject } from 'rxjs';
@Component({
selector: 'test'
template: `<button #myButton>Click me</button>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent implements OnInit {
private _destroyed$: Subject<null> = new Subject();
// reference to template variable
@ViewChild('myButton') myButton: ElementRef<HTMLButtonElement>;
constructor(private _zone: NgZone){}
ngOnInit() {
this.subscribeToMyButton();
}
subscribeToMyButton() {
this._zone.runOutsideAngular(() => {
fromEvent(this.myButton.nativeElement, 'click')
.pipe(takeUntil(this._destroyed$))
.subscribe(e => {
console.log('Yayyyyy! No change detection!');
});
});
}
ngOnDestroy() {
// clean up subscription
this._destroyed$.next();
this._destroyed$.complete();
}
}
希望这对处于类似情况的其他人有所帮助。