Angular 2单击事件回调,不会触发更改检测

时间:2016-09-27 16:28:12

标签: angular angular2-changedetection zonejs

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

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

回调函数执行简单的动画滚动到顶部。它不会影响任何其他组件,因此我不需要在每个组件中进行变化检测,事实上我真的不想让它发射!由于变化检测在每个组件中都会触发,因此动画在移动设备上的表现非常差。

我尝试了什么

我知道我可以使用

在区域之外运行代码
this._zone.runOutsideAngular(() => {
    someFunction();
})

当您要在区域外运行的代码未被clickkeyupkeydown事件等调用时,这很有效。

因为我的组件看起来像......

<button (click)="doThing()">Do that thing outside the zone!</button>

doThing() {
    this._zone.runOutsideAngular(() => {
        myFunction();
    })
}

... myFunction将在区域外运行,但click事件将触发更改检测。

我已经在尽可能多的组件中实施了OnPush更改检测策略。

在此处未触发更改检测非常重要,但我无法找到预防方法。

修改 请参阅此plnk,并在所有组件中将更改检测设置为OnPush。单击子行组件按钮会在子行,行和应用程序组件中触发CD。

3 个答案:

答案 0 :(得分:5)

如上所述,即使将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手动绑定到我想要的任何事件,在这种情况下是点击,使用Observable.fromEvent。此示例还演示了清理事件订阅的做法。

import {Component, ElementRef, OnInit, OnDestroy, NgZone} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';

@Component({
    selector: 'test'
    template: `<button #myButton>Click me</button>`,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent implements OnInit {

    private _subs:Subscription[] = [];

    // reference to template variable
    @ViewChild('myButton') myButton:ElementRef;

    constructor(private _zone:NgZone){}

    ngOnInit() {
        this.manuallyBindToViewEvents();
    }

    manuallyBindToViewEvents() {
        this.subscribeToMyButton()
    }

    subscribeToMyButton() {
        let sub:Subscription;

        this._zone.runOutsideAngular(() => {
            sub = Observable.fromEvent(this.myButton.nativeElement, 'click').subscribe(e => {
                console.log('Yayyyyy! No change detection!');
            })
        });

        this._subs.push(sub);
    }

    ngOnDestroy() {
        // clean up subscriptions
        this._subs.forEach(sub => sub.unsubscribe());
    }

}

希望这会对类似情况下的其他人有所帮助。

答案 1 :(得分:1)

<击> 如果您使用OnPush

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

然后更改检测应限于发生click事件的组件。

您可以分离ChangeDetectorRef

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

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

答案 2 :(得分:1)

我可以展示一个完整的黑客但似乎有效。

<强>子行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;
  }});
}

<强> Demo Plunker