-1

I have a form that submits to a 3rd-party service in order to receive a "token". I can specify a callback function that will handle the response from the 3rd-party service. Ideally, this response will be the token, however it could also be an error message. The callback function is in my controller. In that function, I set some other $scope variables that are required to move forward in the app's flow.

Since I cannot move forward without the updated $scope varible values and they are being set in my callback function, I think I'm stuck using either $watch to trigger some more events when the $scope variables are updated or I can put the rest of my functionality in the callback function.

Option 1 (simplified example):
use $watch on $scope variable to move forward when its value is updated

var myApp = angular.module('myApp',[]);

myApp.controller('GreetingController', ['$scope', function($scope) {

     $scope.token = false;

     $scope.$watch('token', function () {  
         doThis();
         for(var i=0; i<len; i++) {
             myFnction("Do Some", MORE_STUFF);
         }

         someObj.fetchGoodStuff($scope.token);             
     });

     $scope.myCallback = function(status, response) {
         if(!response.error) {
             $scope.token = response.token;
         }
    })
}]);



Option 2 (simplified example):
move forward from within the callback function

var myApp = angular.module('myApp',[]);

myApp.controller('GreetingController', ['$scope', function($scope) {

     $scope.token = false;

     $scope.$watch('token', function () {  
         doThis();
         for(var i=0; i<len; i++) {
             myFnction("Do Some", MORE_STUFF);
         }

         someObj.fetchGoodStuff($scope.token);             
     });

     $scope.myCallback = function(status, response) {
         if(!response.error) {
             $scope.token = response.token;

             doThis();
             for(var i=0; i<len; i++) {
                 myFnction("Do Some", MORE_STUFF);
             }

             someObj.fetchGoodStuff($scope.token); 
         }
    })
}]);



To me, it seems more "correct" to isolate the base functionality, which in this case is receiving the response from a 3rd-party service, of the callback function and put the proceeding functionality elsewhere.

BUT the only other place I can see to put it is in a function called by $watch... and since the value of the $scope variable is only changing once per page visit, $watch does not seem appropriate here.

Does anyone have any insight regarding the best way to take action once a response is received?

Daveh0
  • 952
  • 9
  • 33
  • 1
    Personally, I'd use `$scope.$on` and `$scope.$broadcast` (pub\sub) over watch. Watch fires an awful lot and it's best to avoid it at all costs. Especially since you fire off an event, it makes sense to use pub\sub. – Kyle Muir Jan 09 '15 at 23:12
  • While that's better than watch it's still an unnecessary complication of something that is quite straightforward. Write a big callback, or break it up with a promise. Why go asynchronous? – Peter Ashwell Jan 10 '15 at 00:32

2 Answers2

1

How many times do you need to call the 3rd party service? Do you need to call it every time or do you need to call it once? The common approach is to wrap the 3rd party code with a service that returns a promise.

myApp.factory('My3rdParty', ['$q', '$rootScope', function ($q, $rootScope) {
    return {
        getToken: function () {
            var deferred = $q.defer();
            doSome3rdPartyStuff(function success(token) {
                // we need to trigger a digest because 3rd party lib runs out of
                // angular's digest cycle
                $rootScope.$apply(function (){
                    deferred.resolve(token);
                });
            }, function error(err){
                $rootScope.$apply(function (){
                    deferred.reject(err);
                });
            });
            return deferred.promise;
        }
    }
}]);

In your controller, inject the service and make a call and use the promise to move on

myApp.controller('SomeController', ['$scope', 'My3rdParty', function ($scope, My3rdParty) {
    $scope.doSomeAction = function () {
         My3rdParty.getToken().then(function (token) {
             alert("I got the token!");
         }, function (err) {
             alert("I got an error");
         });
    }; 
}]);

If you need to show the token to the user, you can put that under $scope, If you expect the token to be changed outside of your controller (user changes it, some other service changes it), you might need to $watch it, otherwise the code above should be enough.

Umur Kontacı
  • 35,403
  • 8
  • 73
  • 96
0

Don't use watch. It creates a lot of overhead and headaches later. Only use when absolutely necessary. You should try to use a promise instead, and get your service to return a promise if possible. If you must provide a callback to your outward service (that cannot return a promise) do something like this:

myApp.controller('GreetingController', ['$scope', '$timeout', function($scope, $timeout) {

 $scope.token = false;

// Use this promise to simulate various events related to token fetching
var tokenFetched = $q.defer();

$scope.callbackYouMustHave = function(status, response) {
    if (!response.error) {
        tokenFetched.resolve(response.token); // or any other data you need
    } else {
        tokenFetched.reject(status, response); // Error handling
    }
}

tokenFetched.then(function(token) {
    // Triggering a digest with $timeout, a little awkward but better than watch trust me
    $timeout(function() {
        $scope.token = token; // This triggers a digest and changes your view / whatever
    });
    doThis();
    for(var i=0; i<len; i++) {
        myFnction("Do Some", MORE_STUFF);
    }

    someObj.fetchGoodStuff($scope.token);
});

tokenFetched.catch(function(status, response) {
    // error handling
    console.log('error fetching token:', response);
});

There. Totally clean, totally readable. Don't use watch unless you absolutely must

As an addendum to this I realise if you do use promises you may need to trigger a digest manually. I made another edit

Peter Ashwell
  • 4,292
  • 2
  • 18
  • 22