Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - two-way binding promises - handling rejections

I've found out the hard way that when you two-way bind a promise, angular resolves the promise for you and does bind the actual promise. My question is then, how would one handle a rejected promise?

My particular case is that I have a directive where I two-way-bind a promise from my controller. My controller expects the directive to handle rejection of that promise, since the error would need to be displayed on the DOM.

In my directive I'd expect my bound variable to be a promise, but instead I get the resolved value of that promise. Annoying, but, not terrible.

The issue is when that promise is rejected, the directive has no way of knowing.

See this plunker as an example:

http://plnkr.co/edit/m0cOqFhx6TNrDxTbr9Qx?p=preview

How can I handle the promise rejection in my directive?

Thanks, Roy

like image 738
Roy Truelove Avatar asked Nov 03 '22 23:11

Roy Truelove


2 Answers

I have three suggestions - none which are perfect, but they all work:

  1. Create a getter method that returns the promise and bind this method to the directive using &

  2. Bind the deferred object instead of the promise (ugly)

  3. Broadcast an event when the promise resolves / gets rejected

I've created a plunker with all the suggestions: http://plnkr.co/edit/jsA0PwpQm0xycLclkBU3?p=preview

  • Controller:
app.controller('MainCtrl', function($scope,$q,$timeout) {
  var deferred = $q.defer();

  $scope.promise = deferred.promise;
  $scope.deferred = deferred;

  $scope.getPromise = function(){
    return deferred.promise;
  }

  $scope.promise.then(function(msg){
    $scope.$broadcast('promiseThen',{rejected:false,msg:msg});
  },function(msg){
    $scope.$broadcast('promiseThen',{rejected:true,msg:msg});
  });

  $timeout(function(){deferred.reject('No reason...');},1500)
});
  • Markup:
<div promise-test deferred="deferred" get-promise='getPromise()' ></div>
  • Directive:
app.directive('promiseTest',function(){
  return {
    template:'<div>m1: {{m1}}<br>m2: {{m2}}<br>m3: {{m3}}</div>',
    scope:{getPromise:'&',deferred:'='}
    ,link:function(scope,el,attrs){

      scope.m1 = scope.m2 = scope.m3 = 'Waiting...';

      scope.getPromise().then(function(msg){
        scope.m1 = 'Resolve getPromise: '+msg;
      },function(msg){
        scope.m1 = 'Reject getPromise: '+msg;
      })

      scope.deferred.promise.then(function(msg){
        scope.m2 = 'Resolved deferred.promise: '+msg
      },function(msg){
        scope.m2 = 'Rejected deferred.promise: '+msg
      });

      scope.$on('promiseThen',function(ev,val){
        if(val.rejected){
          scope.m3 = 'Rejected promiseThen: '+val.msg
        }else{
          scope.m3 = 'Resolved promiseThen: '+val.msg          
        }
      })
    }
  }
})
like image 200
joakimbl Avatar answered Nov 11 '22 13:11

joakimbl


The success and error callbacks can be passed as parameters using &.

In this option, the controller is responsible for adding the callbacks to .then.

Controller

app.controller('MainCtrl', function($scope,$q,$timeout) {
  var deferred = $q.defer();

  $scope.register = function(callback, errback) {
    deferred.promise.then(callback, errback);
  }

  $timeout(function(){deferred.reject('No reason...');},1500)
});

Directive

app.directive('promiseTest',function(){
  return {
    template:'<div>m1: {{m1}}',
    scope:{register:'&'},
    link:function(scope,el,attrs){

      scope.m1 = 'Waiting...';

      scope.register({
        success: function(msg){
          scope.m1 = 'Resolve getPromise: '+msg;
        },
        errback: function(msg){
          scope.m1 = 'Reject getPromise: '+msg;
        }
      });
    }
  }
})

HTML

<div promise-test register='register(success, errback)' ></div>

plunkr

like image 22
Joe Grund Avatar answered Nov 11 '22 14:11

Joe Grund