AngularJS: $ scope कॉल करते समय प्रगति पर त्रुटि $ digest को रोकें। $ लागू करें()




angularjs-scope angular-digest (17)

मुझे लगता है कि कोणीय में एक अनुप्रयोग बनाने के बाद से मुझे अपने पृष्ठ को मैन्युअल रूप से मेरे दायरे में अपडेट करने की आवश्यकता है।

ऐसा करने का एकमात्र तरीका यह है कि मैं अपने नियंत्रकों और निर्देशों के दायरे से $apply() को कॉल करना चाहता हूं। इसके साथ समस्या यह है कि यह कंसोल में एक त्रुटि फेंकता रहता है जो पढ़ता है:

त्रुटि: प्रगति पर $ पाचन पहले से ही है

क्या कोई इस त्रुटि से बचने या एक ही चीज़ को प्राप्त करने के बारे में जानता है लेकिन एक अलग तरीके से?


इस पैटर्न का उपयोग न करें - यह समाप्त हो जाने से अधिक त्रुटियों का कारण बन जाएगा। भले ही आपको लगता है कि यह कुछ तय है, ऐसा नहीं हुआ।

$scope.$$phase जांच करके आप जांच सकते हैं कि $digest पहले ही प्रगति पर है या नहीं।

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phase "$digest" या "$apply" वापस आ जाएगा यदि $digest या $apply प्रगति पर है। मेरा मानना ​​है कि इन राज्यों के बीच का अंतर यह है कि $digest वर्तमान दायरे और उसके बच्चों की घड़ियों को संसाधित करेगा, और $apply सभी क्षेत्रों के निरीक्षक को संसाधित करेगा।

@ Dnc253 के बिंदु पर, यदि आप खुद को $digest या $apply अक्सर कॉल करते हैं, तो आप इसे गलत कर सकते हैं। मुझे आमतौर पर पता चलता है कि जब मुझे कोणीय की पहुंच के बाहर एक डोम घटना फायरिंग के परिणामस्वरूप दायरे के राज्य को अद्यतन करने की आवश्यकता होती है तो मुझे पचाने की ज़रूरत होती है। उदाहरण के लिए, जब एक ट्विटर बूटस्ट्रैप मोडल छुपा हो जाता है। कभी-कभी डीओएम घटना तब होती है जब एक $digest प्रगति पर होता है, कभी-कभी नहीं। यही कारण है कि मैं इस चेक का उपयोग करता हूं।

अगर कोई जानता है तो मुझे बेहतर तरीके से जानना अच्छा लगेगा।

टिप्पणियों से: @anddoutoi द्वारा

angular.js विरोधी पैटर्न

  1. if (!$scope.$$phase) $scope.$apply() , इसका मतलब है कि आपका $scope.$apply() कॉल स्टैक में पर्याप्त नहीं है।

आप evalAsync का भी उपयोग कर सकते हैं। पाचन खत्म होने के कुछ समय बाद यह चलेगा!

scope.evalAsync(function(scope){
    //use the scope...
});

इस प्रक्रिया को रखने के लिए आसान छोटी सहायक विधि DRY:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

इस विषय पर कोणीय लोगों के साथ हालिया चर्चा से: भावी-प्रमाणन कारणों के लिए, आपको $$phase उपयोग नहीं करना चाहिए

इसे करने के "सही" तरीके के लिए दबाए जाने पर, जवाब वर्तमान में है

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

फेसबुक, गूगल और ट्विटर एपीआई को लपेटने के लिए कोणीय सेवाओं को लिखते समय मैंने हाल ही में इसमें भाग लिया, जो अलग-अलग डिग्री के लिए कॉलबैक सौंपे गए हैं।

यहां सेवा के भीतर से एक उदाहरण दिया गया है। (अल्पसंख्यक के लिए, शेष सेवा - जो चर सेट अप करती है, इंजेक्शन $ टाइमआउट इत्यादि - को छोड़ दिया गया है।)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

ध्यान दें कि $ टाइमआउट के लिए देरी तर्क वैकल्पिक है और यदि 0 अनसेट हो जाता है तो $timeout 0 डिफ़ॉल्ट हो जाएगा ( $timeout $browser.defer कॉल करता है। अगर देरी सेट नहीं होने पर 0 पर डिफ़ॉल्ट है )

थोड़ा गैर अंतर्ज्ञानी, लेकिन यह कोणीय लिखने वाले लोगों का जवाब है, इसलिए यह मेरे लिए काफी अच्छा है!


उपर्युक्त उत्तरों के समान लेकिन इसने मेरे लिए ईमानदारी से काम किया है ... एक सेवा में जोड़ें:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };

कभी-कभी यदि आप इस तरह से उपयोग करते हैं तो आपको अभी भी त्रुटियां मिलेंगी ( https://.com/a/12859093/801426 )।

इसे इस्तेमाल करे:

if(! $rootScope.$root.$$phase) {
...

पाचन चक्र एक तुल्यकालिक कॉल है। यह तब तक ब्राउज़र के इवेंट लूप पर नियंत्रण नहीं देगा जब तक यह नहीं किया जाता है। इससे निपटने के कुछ तरीके हैं। इसका निपटारा करने का सबसे आसान तरीका $ टाइमआउट में निर्मित करना है, और दूसरा तरीका यह है कि यदि आप अंडरस्कोर या लॉनाश (और आपको होना चाहिए) का उपयोग कर रहे हैं, तो निम्न को कॉल करें:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

या यदि आपके पास अंडरस्कोर है:

_.defer(function(){$scope.$apply();});

हमने कई कामकाज की कोशिश की, और हमने अपने सभी नियंत्रकों, निर्देशों और यहां तक ​​कि कुछ कारखानों में $ रूटस्कोप इंजेक्शन से नफरत की। तो, $ timeout और _.defer अब तक हमारे पसंदीदा रहे हैं। ये विधियां सफलतापूर्वक कोणीय को अगली एनीमेशन लूप तक प्रतीक्षा करने के लिए बताती हैं, जो वर्तमान दायरे की गारंटी देगी। $ आवेदन खत्म हो गया है।


मुझे कोडमिरर जैसे तीसरे पक्ष की स्क्रिप्ट्स के साथ एक ही समस्या थी उदाहरण के लिए और क्रैपानो, और यहां तक ​​कि यहां तक ​​सुरक्षित सुरक्षित विधियों का उपयोग करके मेरे लिए त्रुटि हल नहीं हुई है।

लेकिन क्या हल हो गया है यह $ टाइमआउट सेवा का उपयोग कर रहा है (इसे पहले इंजेक्ट करना न भूलें)।

इस प्रकार, कुछ ऐसा है:

$timeout(function() {
  // run my code safely here
})

और यदि आपके कोड के अंदर आप उपयोग कर रहे हैं

इस

शायद क्योंकि यह एक फैक्ट्री निर्देशक के नियंत्रक के अंदर है या बस किसी प्रकार की बाध्यकारी की आवश्यकता है, तो आप ऐसा कुछ करेंगे:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

मैं इस विधि का उपयोग कर रहा हूं और ऐसा लगता है कि यह पूरी तरह से ठीक काम करता है। यह सिर्फ चक्र के समाप्त होने के समय की प्रतीक्षा करता है और फिर ट्रिगर apply() । बस उस फंक्शन को apply(<your scope>) कहीं भी आप चाहते हैं।

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}

मैं उन समस्याओं पर $apply होने के बजाय $eval को कॉल करके इस समस्या को हल करने में सक्षम हूं, जहां मुझे पता है कि $digest फ़ंक्शन चल रहा है।

docs मुताबिक, $apply मूल रूप से $apply होता है:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

मेरे मामले में, एक ng-click एक चर के भीतर एक चर बदलता है, और उस चर पर एक $ घड़ी अन्य चर बदलती है जिसे $applied जाना चाहिए। यह अंतिम चरण त्रुटि "पाचन पहले ही प्रगति पर है" का कारण बनता है।

घड़ी अभिव्यक्ति के अंदर $eval साथ $eval $apply प्रतिस्थापित करके, स्कोप चर को अपेक्षित के रूप में अपडेट किया जाता है।

इसलिए, ऐसा प्रतीत होता है कि यदि कोणीय के भीतर कुछ अन्य परिवर्तनों के कारण पाचन किसी भी तरह से चल रहा है, तो आपको केवल $eval 'ing करना है।


यह पाया गया: https://coderwall.com/p/ngisma जहां नाथन वाकर (पृष्ठ के निचले भाग के पास) $# में एक सजावट का सुझाव देता है funccope func 'safeApply' बनाने के लिए, कोड:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);

यह मेरी यूटिल सेवा है:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

और यह इसके उपयोग के लिए एक उदाहरण है:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};

यहां कई उत्तरों में अच्छी सलाहएं हैं लेकिन भ्रम पैदा कर सकती हैं। बस $timeout का उपयोग करना सबसे अच्छा और न ही सही समाधान है। साथ ही, यह सुनिश्चित करना सुनिश्चित करें कि यदि आप प्रदर्शन या स्केलेबिलिटी से चिंतित हैं।

चीजें आपको जानना चाहिए

  • $$phase ढांचे के लिए निजी है और इसके लिए अच्छे कारण हैं।

  • $timeout(callback) वर्तमान पाचन चक्र (यदि कोई हो) पूरा होने तक प्रतीक्षा करेगा, फिर कॉलबैक निष्पादित करें, फिर अंत में एक पूर्ण $apply चलाएं।

  • $timeout(callback, delay, false) वही करेगा (कॉलबैक निष्पादित करने से पहले वैकल्पिक देरी के साथ), लेकिन यदि आप अपने कोणीय मॉडल को संशोधित नहीं करते हैं तो $apply (तीसरा तर्क) नहीं होगा जो प्रदर्शन को बचाता है ($ स्कोप )।

  • $scope.$apply(callback) अन्य चीज़ों के साथ, $rootScope.$digest , जिसका अर्थ है कि यह एप्लिकेशन और उसके सभी बच्चों के मूल दायरे को फिर से शुरू करेगा, भले ही आप एक अलग दायरे में हों।

  • $scope.$digest() बस अपने मॉडल को देखने के लिए सिंक करेगा, लेकिन अपने माता-पिता के दायरे को पच नहीं पाएगा, जो आपके एचटीएमएल के एक अलग हिस्से पर काम करते समय बहुत सारे प्रदर्शनों को बचा सकता है (ज्यादातर निर्देशों से) । $ पाचन कॉलबैक नहीं लेता है: आप कोड निष्पादित करते हैं, फिर पचते हैं।

  • $scope.$evalAsync(callback) angularjs 1.2 के साथ पेश किया गया है, और शायद आपकी अधिकांश परेशानियों को हल करेगा। इसके बारे में अधिक जानने के लिए कृपया अंतिम पैराग्राफ देखें।

  • यदि आपको $digest already in progress error मिल गया है, तो आपका आर्किटेक्चर गलत है: या तो आपको अपने दायरे को फिर से शुरू करने की आवश्यकता नहीं है, या आप इसके प्रभारी नहीं होना चाहिए (नीचे देखें)।

अपने कोड को कैसे व्यवस्थित करें

जब आपको यह त्रुटि मिलती है, तो आप अपने दायरे को पचाने की कोशिश कर रहे हैं, जबकि यह पहले से ही प्रगति पर है: चूंकि आप उस बिंदु पर अपने दायरे की स्थिति नहीं जानते हैं, इसलिए आप इसके पाचन से निपटने का प्रभारी नहीं हैं।

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

और यदि आप जानते हैं कि आप क्या कर रहे हैं और एक बड़े कोणीय अनुप्रयोग के हिस्से के दौरान एक अलग छोटे निर्देश पर काम कर रहे हैं, तो आप प्रदर्शन को बचाने के लिए $ $ के बजाय $ पाचन पसंद कर सकते हैं।

Angularjs 1.2 के बाद अद्यतन करें

किसी भी $ स्कोप में एक नई, शक्तिशाली विधि जोड़ा गया है: $evalAsync । असल में, यदि कोई होता है तो यह वर्तमान पाचन चक्र के भीतर अपनी कॉलबैक निष्पादित करेगा, अन्यथा नया पाचन चक्र कॉलबैक निष्पादित करना शुरू कर देगा।

यह अभी भी एक $scope.$digest रूप में अच्छा नहीं है $scope.$digest अगर आप वास्तव में जानते हैं कि आपको केवल अपने HTML के एक अलग हिस्से को सिंक्रनाइज़ करने की आवश्यकता है (क्योंकि कोई नया $apply होगा यदि कोई प्रगति पर नहीं है), लेकिन यह सबसे अच्छा है समाधान जब आप किसी फ़ंक्शन को निष्पादित कर रहे होते हैं जिसे आप इसे नहीं जानते हैं, तो सिंक्रनाइज़ेशन निष्पादित किया जाएगा या नहीं , उदाहरण के लिए संसाधन को संभावित रूप से कैश करने के बाद: कभी-कभी इसे किसी सर्वर पर एसिंक कॉल की आवश्यकता होगी, अन्यथा संसाधन स्थानीय रूप से सिंक्रनाइज़ किया जाएगा।

इन मामलों में और अन्य सभी जहां आपके पास था !$scope.$$phase , $scope.$evalAsync( callback ) का उपयोग करना सुनिश्चित करें $scope.$evalAsync( callback )


वर्षफूमो ने एक पुन: प्रयोज्य $ सुरक्षित बनाने के लिए एक महान काम किया है हमारे लिए आवेदन करें:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

उपयोग:

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);

सबसे पहले, इसे इस तरह ठीक न करें

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

यह समझ में नहीं आता है क्योंकि $ चरण $ पाचन चक्र के लिए सिर्फ एक बुलियन ध्वज है, इसलिए आपका $ लागू () कभी-कभी नहीं चलाएगा। और याद रखें कि यह एक बुरा अभ्यास है।

इसके बजाय, $timeout उपयोग $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

यदि आप अंडरस्कोर या लॉनाश का उपयोग कर रहे हैं, तो आप defer () का उपयोग कर सकते हैं:

_.defer(function(){ 
  $scope.$apply(); 
});

सुरक्षित $apply का सबसे छोटा रूप है:

$timeout(angular.noop)

$scope.$$phase || $scope.$apply(); उपयोग करें $scope.$$phase || $scope.$apply(); $scope.$$phase || $scope.$apply(); बजाय





angular-digest