Angular: Disable Change Detector for an entire class (service or component)

后端 未结 3 486
终归单人心
终归单人心 2021-01-24 20:07

Is there a way to completely disable Angular\'s change detector if events that normally cause the change detection to run (setTimeout, setInterval, browser events, ajax calls et

相关标签:
3条回答
  • 2021-01-24 20:39

    Change Detection starts at the very top of your component tree and is triggered by zone.js. An async operation like setTimeout is picked up by zone.js which notifies angular that changes might have happened. Angular then runs change detection top-down on the component tree. Detaching a single class from the change detector will cut out that class (i.e. directive) from change detection but won't stop change detection from being run on the rest of your component tree. For your needs ngZone.runOutsideAngular(...) is the way to go, because it stops zone.js from notifying angular about your async operation thus entirely preventing change detection from being run.

    0 讨论(0)
  • 2021-01-24 20:49

    You can change the detection strategy of individual components with the following:

    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 {
    
    }
    

    I dont know any other method that could archieve some selective turn on/of depending of where is the information coming from

    0 讨论(0)
  • 2021-01-24 20:50

    One possible solution might be the following @RunOutsideAngular decorator for your service:

    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 Example

    If you want to disable only setTimeout and setInterval within some class you can patch these functions

    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']);
    

    and create decorator like this

    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;
    }
    

    Then you can use it as follows

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

    Plunker Example

    0 讨论(0)
提交回复
热议问题