4

我在尝试(click)在 Angular 2 中的事件回调函数中执行一些逻辑而不触发更改检测时遇到了很大的麻烦。

为什么我不想触发变更检测

回调函数执行简单的动画滚动到顶部。它不会影响其他组件,因此我不需要在每个组件中触发更改检测,事实上我真的不希望它触发!因为更改检测确实会在每个组件中触发,所以动画在移动设备上的性能非常差。

我试过的

我知道我可以使用在区域外运行代码

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。

4

3 回答 3

8

如前所述,即使设置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();
    }

}

希望这将对处于类似情况的其他人有所帮助。

于 2016-09-28T10:15:05.993 回答
1

如果你使用OnPush

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
})

那么更改检测应该仅限于发生点击事件的组件。

你可以拆开ChangeDetectorRef

  constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {
    ref.detach();
  }

在重新附加之前,不会对此组件运行更改检测。

于 2016-09-27T16:33:37.180 回答
1

我可以展示一个完整的技巧,但它似乎有效。

子行组件.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;
  }});
}

演示 Plunker

于 2016-09-28T05:49:48.707 回答