Found solution
Promises : go to next error function Just need to rethrow the error.
Why promise's reference doesn't execute then's error function when spying on it?
Having two buttons returning (calling) same promise, one executes ok ($scope.expected() - button renders red), other goes in to success function ($scope.notExpected() - button renders green) and it is not expected behavour.
Current solution is, to wrap in another promise;
var app = angular.module('thenApp', []);
function ThenCtrl($scope, thenService, $q) {
$scope.expected = function() {
return thenService.doIt()
.then(function() {
console.log('was a success');
});
};
$scope.notExpected = function() {
return thenService.doIt()
.then(function() {
console.log('was a success');
}, function() {
console.log('has failed');
});
};
$scope.solution = function() {
var deferred = $q.defer();
thenService.doIt()
.then(function() {
deferred.resolve();
console.log('was a success');
}, function() {
deferred.reject();
console.log('has failed');
});
return deferred.promise;
};
}
app.controller('ThenCtrl', ThenCtrl);
var CLICK_EVENT = 'click';
var TIMEOUT_TO_END_ANIMATION = 1000;
var SUCCESS_CLASS = 'btn-success success';
var FAIL_CLASS = 'btn-error error';
var LOADING_CLASS = 'loading';
/**
* Inspiration from https://github.com/johannesjo/angular-promise-buttons
* @param $parse
* @param $timeout
* @returns {{scope: {promise: string, stateRedirect: string}, link: link}}
*/
function btnLoader($parse, $timeout) {
return {
restrict: 'A',
require: '?ngClick',
scope: true,
link: function(scope, el, attrs) {
el.addClass('btn-load');
var promiseWatcher;
// we need to use evalAsync here, as
// otherwise the click or submit event
// won't be ready to be replaced
scope.$evalAsync(function() {
var cb = $parse(attrs.ngClick);
function buttonLoader() {
// Make sure we run the $digest cycle
scope.$apply(function() {
var promise = cb(scope.$parent, {
$event: CLICK_EVENT
});
// only init watcher if not done before
if (!promiseWatcher) {
// watch promise to resolve or fail
promiseWatcher = scope.$watch(function() {
return promise;
}, function(nVal) {
// for regular promises
if (nVal && nVal.then) {
el.unbind(CLICK_EVENT);
el.addClass(LOADING_CLASS);
el.removeClass(SUCCESS_CLASS);
el.removeClass(FAIL_CLASS);
nVal.then(function() {
// promise was a success
el.addClass(SUCCESS_CLASS);
if (attrs.alwaysBind) {
el.bind(CLICK_EVENT, buttonLoader);
}
}, function() {
// promise was a fail
el.addClass(FAIL_CLASS);
el.bind(CLICK_EVENT, buttonLoader);
}).finally(function() {
el.removeClass(LOADING_CLASS);
promiseWatcher();
promiseWatcher = null;
$timeout(function() {
el.removeClass(SUCCESS_CLASS);
el.removeClass(FAIL_CLASS);
}, TIMEOUT_TO_END_ANIMATION);
});
}
});
}
});
}
// unbind original click event
el.unbind(CLICK_EVENT);
// rebind, but this time watching it's return value
el.bind(CLICK_EVENT, buttonLoader);
});
}
};
}
app.directive('btnLoader', btnLoader);
function thenService($q, $timeout) {
thenService.doIt = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.reject(null);
}, 500);
return deferred.promise;
};
return thenService;
}
app.factory('thenService', thenService);
.loading {
background: yellow !important;
}
.btn-error {
background: red !important;
}
.btn-success {
background: green !important;
}
.btn {
margin-bottom: 20px;
border: 1px solid rgba(0, 0, 0, 0.3);
background: white;
width: 250px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
.btn:hover {
background: lightgray;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="thenApp" ng-controller="ThenCtrl">
<p>
Button action does not spy on then's error function.
</p>
<div class="btn" btn-loader="" always-bind="true" ng-click="expected()">
As expected
</div>
<p>
Button action spies on then's error function.
</p>
<div class="btn" btn-loader="" always-bind="true" ng-click="notExpected()">
Not expected
</div>
<p>
Solution for spying on then's error function.
</p>
<div class="btn" btn-loader="" always-bind="true" ng-click="solution()">
Solution
</div>
</div>