0

I'm using the library ParallelJS to do encryption/decryption in a web worker but when the promise is resolved it doesn't update the view accordly to the model's changes. Now, i know that i have to wrap the code that is called outside angularjs' scope in a $scope.$apply, but even doing this doesn't help.

I think that the reason is that I am resolving a deferred object inside a callback that is called outside angular's scope. It's a little hard to explain so let me show my code:

function _encrypt(options){
... //crypto-js code to do AES encryption
}

function _decrypt(options){
... //crypto-js code to do AES decryption
}

angular.module('CryptoService', []).factory('Cryptor', function($q, $rootScope){
    function Cryptor(){};
    Cryptor.prototype = {
        encrypt: function(string, key) {
            var deferred = $q.defer();
            var ivS = generateIV();
            var p = new Parallel({
                iv: ivS,
                text: string,
                key: key
            }, { evalPath: '/assets/js/eval.min.js' });

            p.require('/assets/js/crypto.min.js');

            p.spawn(_encrypt).then(function(result){
                deferred.resolve(result);
            });

            return deferred.promise;
        },
        decrypt: function(string, key) {
            var deferred = $q.defer();
            var p = new Parallel({
                text: string,
                key: key
            }, { evalPath: '/assets/js/eval.min.js' });

            p.require('/assets/js/crypto.min.js');

            p.spawn(_decrypt).then(function(result){
                deferred.resolve(result);
            });
            return deferred.promise;
        }
    };
    return new Cryptor();
});

angular.module('ContactService', ['CryptoService']).factory('Contact', function($q, $rootScope, Cryptor){
    function Contact(){
        //initialization
    };
    Contact.prototype = {
       query: function(){
           var deferred = $q.defer();
           var options = {};
           _oauth.secureGET(this._endpoint,options).done(function(result){
               Cryptor.decrypt(result.cmc, key).then(function(string){
                   var data = JSON.parse(string);
                   var contacts = [];
                   for (var cidx in data){
                       var objContact = data[cidx];
                       var c = new Contact();
                       for(var pidx in this._properties){
                           var property = this._properties[pidx];
                           c[property] = objContact[property];
                       }
                       contacts.push(c);
                   }
                   //Since _oauth is using a jQuery method to execute the requests we are outside of angularjs' scope, so we need to wrap the promise resolution in 
                   //the $apply method of the rootscope
                   $rootScope.$apply(function(){
                       deferred.resolve(contacts);
                   });
               });

           }.bind(this)).fail(function() {
               $rootScope.$apply(function(){
                   deferred.resolve([]);
               });
           });
           return deferred.promise;
       },
   };
   return new Contact();
});

Now what's happen: If I leave the code the callback function of the query method is never called, since in the cryptor service the promise is called outside angular's scope. If I move the $rootScope.$apply wrapper to the Cryptor service then the callback inside the Contact service is called, the callback inside the controller is called BUT the view is not updated.

Any hint on how to resolve this?

Thanks you all

A.

Antonio E.
  • 4,381
  • 2
  • 25
  • 35

2 Answers2

1

Ok I feel so stupid... The problem wasn't the view not updating but the model empty. Since i was missing a bind(this) to the Cryptor promise's callback the models were empty and the view wasn't showing anything. Changing this

Cryptor.decrypt(result.cmc, key).then(function(string){
    var data = JSON.parse(string);
    var contacts = [];
    for (var cidx in data){
        var objContact = data[cidx];
        var c = new Contact();
        for(var pidx in this._properties){
            var property = this._properties[pidx];
            c[property] = objContact[property];
        }
        contacts.push(c);
    }
    //Since _oauth is using a jQuery method to execute the requests we are outside of angularjs' scope, so we need to wrap the promise resolution in 
    //the $apply method of the rootscope
    $rootScope.$apply(function(){
        deferred.resolve(contacts);
    });
});

to this:

Cryptor.decrypt(result.cmc, key).then(function(string){
    ...
}.bind(this));

did the trick.

Antonio E.
  • 4,381
  • 2
  • 25
  • 35
0

From your code:

p.spawn(_encrypt).then(function(result){
    deferred.resolve(result); 
});

Promise from Parallel.js is not same promise which in Angular.js. So you need to wrap

deferred.resolve(result);

into Angular.js $timeout:

$timeout(function(){
    deferred.resolve(result);
}, 0)

in order to it notify Angular.js

kostik
  • 1,179
  • 10
  • 11
  • Since later (in the contact service) I am resolving the other promise inside a $rootScope.$apply method your code leads to a double $digest error. Removing the $rootScope.$apply allow the promises to be resolved up to the controller but the view doesn't update – Antonio E. Sep 04 '13 at 13:24
  • $digest error: Look this http://stackoverflow.com/questions/12729122/prevent-error-digest-already-in-progress-when-calling-scope-apply This is why I propose to use $timeout instead $apply – kostik Sep 04 '13 at 20:39
  • I've understood what you're saying but probably $timeout force a $digest so I cannot use it since i subsequently do a $apply – Antonio E. Sep 05 '13 at 06:52
  • Just read this http://stackoverflow.com/questions/12729122/prevent-error-digest-already-in-progress-when-calling-scope-apply – kostik Sep 05 '13 at 10:34