typescript - Angular: Tüm bir sınıf (servis veya bileşen) için Değişiklik Algılayıcısını devre dışı bırak


Normalde değişiklik tespitinin çalışmasına neden olan olaylar (setTimeout, setInterval, tarayıcı olayları, ajax çağrıları vb.) belirli bir sınıftan (servis veya bileşen) geliyorsa, Angular'ın değişiklik algılayıcısını tamamen devre dışı bırakmanın bir yolu var mı?

Yani, servisimde kaydedilen setInterval'in her saniye global değişiklik tespitini çalıştırdığını öğrendiğimde bana tamamen yanlış göründü..

Kodumu NgZone.runOutsideAngular metodunun geri çağrısının içine sarabileceğimin farkındayım, ama değişiklik algılayıcısının tüm bir sınıf için devre dışı bırakılabileceği çözümü tercih ederim, çünkü serviste algılamayı gereksiz yere çalıştıran başka kod parçalarım da var.

Yardımın için teşekkürler.

Cevap #1

Olası bir çözüm, servisinize yönelik aşağıdaki @RunOutsideAngular dekoratörü olabilir:

declare let Zone: any;

export function RunOutsideAngular(target: any) {
  Object.getOwnPropertyNames(target.prototype)
    .filter(p => typeof target.prototype[p] === 'function')
    .forEach(p => {
      let originalMethod = target.prototype[p];  
      target.prototype[p] = function (...args) {
        let self = this;
        Zone.root.run(() => originalMethod.apply(self, args));
      }
    });

  let ctor: any = function (...args) {
    let self = this;
    return Zone.root.run(() => target.apply(self, args));
  };
  ctor.prototype = target.prototype;
  return ctor;
}

Plunker Örneği

Eğer bir sınıf içinde sadece setTimeout ve setInterval işlevlerini devre dışı bırakmak istiyorsanız, bu işlevleri yamalayabilirsiniz

function patchTimers(timers: any[]) {
    timers.forEach((timer) => {
        let originalMethod = window[timer];
        window[timer] = function (...args) {
            let self = this;
            if (Zone.current['__runOutsideAngular__'] === true && Zone.current.name === 'angular') {
                Zone.root.run(() => originalMethod.apply(self, args));
            } else {
                originalMethod.apply(this, arguments);
            }
        };
    })
}
patchTimers(['setTimeout', 'setInterval']);

ve bu şekilde bir dekoratör oluştur

export function RunOutsideAngular(target: any) {
    Object.getOwnPropertyNames(target.prototype)
        .filter(p => typeof target.prototype[p] === 'function')
        .forEach(p => {
            let originalMethod = target.prototype[p];
            target.prototype[p] = function (...args) {
                Zone.current['__runOutsideAngular__'] = true;
                originalMethod.apply(this, args);
                delete Zone.current['__runOutsideAngular__'];
            }
        });

    let ctor: any = function (...args) {
        Zone.current['__runOutsideAngular__'] = true;
        let instance = target.apply(this, args);
        delete Zone.current['__runOutsideAngular__'];
        return instance;
    };
    ctor.prototype = target.prototype;
    return ctor;
}

O zaman bunu şu şekilde kullanabilirsiniz

@RunOutsideAngular
export class Service {
  constructor() {
    setInterval(() => {
      console.log('ctor tick');
    }, 1000);
  }

  run() {
    setTimeout(() => {
      console.log('tick');
    }, 1000);

    setInterval(() => {
      console.log('tick interval');
    }, 1000)
  }
}

Plunker Örneği

Cevap #2

Bireysel bileşenlerin algılama stratejisini aşağıdakilerle değiştirebilirsiniz:

import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'ws-layout-page',
  templateUrl: './layout-page.component.html',
  styleUrls: ['./layout-page.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class LayoutPageComponent {

}

Bilginin nereden geldiğine bağlı olarak seçici açma/kapama sağlayabilecek başka bir yöntem bilmiyorum.

İlgili makaleler