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




angularjs-scope angular-digest (20)

यह समझना कि कोणीय दस्तावेज $$phase को एंटी-पैटर्न की जांच करते हैं, मैंने $timeout और _.defer . कार्य करने के लिए प्रयास किया।

टाइमआउट और स्थगित विधियां एक FOUT जैसे डोम में FOUT {{myVar}} सामग्री का एक FOUT । मेरे लिए यह स्वीकार्य नहीं था। यह मुझे बिना किसी बात के बताता है कि कुछ हैक है, और कोई उपयुक्त विकल्प नहीं है।

एकमात्र चीज जो हर समय काम करती है वह है:

if(scope.$$phase !== '$digest'){ scope.$digest() }

मैं इस विधि के खतरे को नहीं समझता, या टिप्पणियों और कोणीय टीम में लोगों द्वारा इसे हैक के रूप में क्यों वर्णित किया गया है। आदेश सटीक और पढ़ने में आसान लगता है:

"जब तक कोई पहले से ही नहीं हो रहा है तब तक पाचन करें"

कॉफीस्क्रिप्ट में यह भी सुंदर है:

scope.$digest() unless scope.$$phase is '$digest'

इसके साथ क्या समस्या है? क्या कोई विकल्प है जो एक फूट नहीं बनाएगा? https://github.com/yearofmoo/AngularJS-Scope.SafeApply ठीक दिखता है लेकिन $$phase निरीक्षण विधि का भी उपयोग करता है।

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

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

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

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


उपयोग करने का प्रयास करें

$scope.applyAsync(function() {
    // your code
});

के बजाय

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

$ applyAsync $ के आवेदना को बाद में लागू होने पर लागू करें। इसका उपयोग कई अभिव्यक्तियों को कतार में करने के लिए किया जा सकता है जिन्हें उसी पाचन में मूल्यांकन करने की आवश्यकता होती है।

नोट: $ पाचन के भीतर, $ applyAsync () केवल तभी फ़्लश करेगा जब वर्तमान दायरा $ rootScope है। इसका मतलब यह है कि यदि आप बच्चे के दायरे पर $ पाचन कहते हैं, तो यह निश्चित रूप से $ applyAsync () कतार को फ्लश नहीं करेगा।

उदाहरण के:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

संदर्भ:

1. स्कोप। $ लागू एसिंक () बनाम स्कोप। $ EvalAsync () AngularJS 1.3 में

  1. AngularJs डॉक्स

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

$timeout(angular.noop)

मैं उन समस्याओं पर $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 करना है।


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

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

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

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

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


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

    //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) {
...


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


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

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

मैं आपको एक पाचन चक्र ट्रिगर करने के बजाए एक कस्टम घटना का उपयोग करने की सलाह दूंगा।

मुझे यह पता चला है कि इस कार्यक्रम के लिए श्रोताओं को पंजीकृत करना और श्रोताओं को पंजीकृत करना एक ऐसी क्रिया को ट्रिगर करने का एक अच्छा समाधान है, जो आप चाहते हैं कि आप पाचन चक्र में हों या नहीं।

एक कस्टम इवेंट बनाकर आप अपने कोड के साथ और अधिक कुशल भी हो रहे हैं क्योंकि आप केवल उस घटना के लिए सब्सक्राइब किए गए श्रोताओं को ट्रिगर कर रहे हैं और यदि आप स्कोप का आह्वान करते हैं तो आप सभी घड़ियों को दायरे में घुमाएंगे।

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);

मैं इस विधि का उपयोग कर रहा हूं और ऐसा लगता है कि यह पूरी तरह से ठीक काम करता है। यह सिर्फ चक्र के समाप्त होने के समय की प्रतीक्षा करता है और फिर ट्रिगर 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);
  }
}

इस विषय पर कोणीय लोगों के साथ हालिया चर्चा से: भावी-प्रमाणन कारणों के लिए, आपको $$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 पर डिफ़ॉल्ट है )

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


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

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

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

$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();

  }]
)

आप उपयोग कर सकते हैं

$timeout

त्रुटि को रोकने के लिए।

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);

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

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

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

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

http://docs.angularjs.org/error/$rootScope:inprog देखें

समस्या तब उत्पन्न होती है जब आपके पास $apply करने के लिए कॉल $apply जिसे कभी-कभी एंगुलर कोड (जब $ लागू होता है) के बाहर असीमित रूप से चलाया जाता है और कभी-कभी कोणीय कोड के अंदर सिंक्रनाइज़ होता है (जिसके कारण $digest already in progress त्रुटि में होता है)।

ऐसा हो सकता है, उदाहरण के लिए, जब आपके पास एक लाइब्रेरी है जो असीमित रूप से सर्वर से आइटम लेती है और उन्हें कैश करती है। पहली बार किसी आइटम का अनुरोध किया जाता है, इसे कोड निष्पादन को अवरुद्ध न करने के लिए अतुल्यकालिक रूप से पुनर्प्राप्त किया जाएगा। दूसरी बार, हालांकि, आइटम पहले से ही कैश में है इसलिए इसे समकालिक रूप से पुनर्प्राप्त किया जा सकता है।

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

उपाय

संक्षेप में, ऐसा करने के बजाय:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

यह करो:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

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

जब तक कोई $timeout से अधिक $timeout पर $timeout का उपयोग करने के लिए कुछ प्रभावशाली नुकसान के बारे में पता नहीं है, तो मुझे नहीं लगता कि आप $apply बजाय हमेशा $timeout (शून्य देरी के साथ) क्यों नहीं उपयोग कर सकते हैं, क्योंकि यह लगभग एक ही चीज़ करेगा।


यह आपकी समस्या का समाधान करेगा:

if(!$scope.$$phase) {
  //TODO
}

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

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




angular-digest