Endre
Endre

Reputation: 50

executing Angular JS code after some jQuery.ajax has executed

I want to update an Angular scope with data returned by some jQuery ajax call. The reason why I want to call the Ajax from outside Angular is that a) I want the call to return as fast as possible, so it should start even before document.ready b) there are multiple calls that initialize a complex model for a multiple-page web app; the calls have dependencies among themselves, and I don't want to duplicate any logic in multiple Angular controllers.

This is some code from the controller. Note that the code is somewhat simplified to fit here.

$scope.character = {};
$scope.attributeArray = [];
$scope.skillArray = [];

The reasoning for this is that a character's attributes and skills come as objects, but I display them using ng-repeat, so I need them as arrays.

$scope.$watch('character',function(){
  $scope.attributeArray = getAttributeArray($scope.character);
  $scope.skillArray = getSkillArray($scope.character);
});

In theory, when $scope.character changes, this piece of code updates the two arrays. Now comes the hard part. I've tried updating $scope.character in two ways:

characterRequestNotifier.done(function() { // this is a jQuery deferred object
  $scope.$apply(function(){ // otherwise it's happening outside the Angular world
    $scope.character = CharacterRepository[characterId]; // initialized in the jquery ajax call's return function
  });
});

This sometimes causes $digest is already in progress error. The second version uses a service I've written:

repository.getCharacterById($routeParams.characterId, function(character){
  $scope.character = character;
});

, where

.factory('repository', function(){
  return {
    getCharacterById : function(characterId, successFunction){
      characterRequestNotifier.done(function(){
        successFunction( CharacterRepository[characterId] );
      });
    }
  };
});

This doesn't always trigger the $watch.

So finally, the question is: how can I accomplish this task (without random errors that I can't identify the source of)? Is there something fundamentally wrong with my approaches?

Edit:

Try this jsfiddle here: http://jsfiddle.net/cKPMy/3/ This is a simplified version of my code. Interestingly, it NEVER triggers the $watch when the deferred is resolved.

Upvotes: 0

Views: 1521

Answers (1)

Martin
Martin

Reputation: 8866

It is possible to check whether or not it is safe to call $apply by checking $scope.$$phase. If it returns something truthy--e.g. '$apply' or '$digest'--wrapping your code in the $apply call will result in that error message.

Personally I would go with your second approach, but use the $q service--AngularJS's promise implementation.

.factory('repository', function ($q) {
  return {
    getCharacterById : function (characterId) {
      var deferred = $q.defer();

      characterRequestNotifier.done(function () {
        deferred.resolve(CharacterRepository[characterId]);
      });

      return deferred.promise;
    }
  };
});

Since AngularJS has native support for this promise implementation it means you can change your code to:

$scope.character = repository.getCharacterById(characterId);

When the AJAX call is done, the promise is resolved and AngularJS will automatically take care of the bindings, trigger the $watch etc.

Edit after fiddle was added

Since the jQuery promise is used inside the service, Angular has no way of knowing when that promise is resolved. To fix it you need to wrap the resolve in an $apply call. Updated fiddle. This solves the fiddle, I hope it solves your real problem too.

Upvotes: 1

Related Questions