4

(click)変更検出をトリガーせずに、Angular 2のイベント コールバック関数内でいくつかのロジックを実行しようとすると、大きな問題が発生します。

変更検出をトリガーしたくない理由

コールバック関数は、上への単純なアニメーション スクロールを実行します。他のコンポーネントには影響を与えないため、すべてのコンポーネントで変更検出を起動する必要はありません。実際、起動させたくありません! 変更検出はすべてのコンポーネントで実行されるため、モバイルでのアニメーションのパフォーマンスは非常に低くなります。

私が試したこと

を使用してゾーン外でコードを実行できることを知っています

this._zone.runOutsideAngular(() => {
    someFunction();
})

clickこれは、ゾーンの外で実行したいコードが、keyupkeydownイベントなどによって呼び出されない場合にうまく機能します。

私のコンポーネントは次のように見えるので...

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

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

...myFunctionゾーン外で実行されますが、clickイベントは変更検出をトリガーします。

OnPush可能な限り多くのコンポーネントに変更検出戦略を既に実装しています。

ここで変更検出がトリガーされないことが重要ですが、それを防ぐ方法が見つかりません。

編集すべてのコンポーネントで変更検出が設定されて いるこのplnkを参照してください。OnPushsubrow コンポーネント ボタンをクリックすると、subrow、row、および app コンポーネントで 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

1 つの完全なハックを示すことができますが、うまくいくようです。

sub-row-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;
  }});
}

デモプランカー

于 2016-09-28T05:49:48.707 に答える
1

使用する場合OnPush

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

次に、変更の検出は、クリック イベントが発生したコンポーネントに限定する必要があります。

を取り外すことができますChangeDetectorRef

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

このコンポーネントが再接続されるまで、変更検出は実行されません。

于 2016-09-27T16:33:37.180 に答える