Angular Promise Conditional - angularjs

My Angular v1.x directive calls a function when a button is clicked and depending on the input I set some variables:
$scope.offerRespond = function() {
if (offer.i.offerResponse == 1) {
// set some variables
}
else if (offer.i.offerResponse == 0) {
$http({method: 'GET', url: frontbaseurl+'/offer_types/' + id + '.json'})
.then(function successCallback(response) {
$scope.reason = response.data.data.name;
}, function errorCallback() {
$scope.reason = "Unknown";
}
);
$http.post(frontbaseurl+'/purchases.json', JSON.stringify(dataObj))
.then(function(response){
// continue....
}
}
As can be seen, I make a GET request if the offerResponse == 0. This seems risky because the code continues to execute without before a response from the request is received. I know the approach is to use a 'promise' but how does that apply in this case if the GET request may not be made?
The problem as I see it is if I add a promise and the offerResponse == 1 the promise can't be fulfilled since the GET request would not have been made.
Any suggestions please?

I've had to do something similar in our angular 1.x app, using Typescript, like this:
public myFunc = async (id): Promise<void> => {
return new Promise<void>(async (resolve, reject) => {
if (this.offer.i.offerResponse === 1) {
// set some variables
resolve();
}
else if (this.offer.i.offerResponse === 0) {
try {
const response = await services.getOfferTypes(id);
this.reason = response.data.data.name;
resolve();
} catch (errorResponse) {
this.reason = "Unknown";
reject();
}
}
});
}
Notice I "encapsulated" your http request in a service function. I like separating api calls into their own service, to keep things clean and easy to read. This code is untested, but if I remember correctly it should work something like this.

Related

Angular ng-table loading from server with pagination

I am trying to figure out how to populate an ng-table and apply the total value for the params. It appears there are examples that are showing almost what I'm doing - but not exactly and the total never gets set.
$scope.tableParams = new NgTableParams({page: 1, count: 25},
{
counts: [],
getData: function(params)
if (CompaniesView.ViewInitialized)
return CompaniesView.RetrieveCompanies($scope, $http, function ()
{
params.total($scope.RequestFilter.TotalCompanies);
return $scope.TableParams.data;
});
}
});
My RetrieveCompanies function retrieves he data - and will call a callback function on completion. I was under the impression at this point I could set the params.total, but is not working. I see that examples are doing similar, except they are performing a jQuery API operation directly. I am not sure why this does not work. I would have thought anytime setting the total would cause the table pagination controls to update - but it appears it has to be done within the detData call. But if you are making an async call how can we have the total set in the call since it won't have data until the async completes - which means the
getData call has already finished.
Probably missing some concept here -- but I am not an expert when it comes to jQuery or angular.
Peter
Update:
RetrieveCompanies function
RetrieveCompanies: function (scope, http,callback)
{
scope.IsLoading = true;
var Data =
{
AuthoirzationType: TokenRetrieval.SessionAuth,
SessionId: activeSession
};
var tokenRequest =
{
params: Data
};
performVeilabilityTokenRetrieval(http, tokenRequest,
function (response)
{
if (true)
{
if (!response.HasError)
{
scope.RequestFilter.State = scope.selectedState.Value
scope.RequestFilter.regionsearch = scope.selectedRegion.id;
scope.RequestFilter.checkadminStaff = scope.selectedAdmin.value;
//tableParams
scope.RequestFilter.Page = scope.tableParams.page();
scope.RequestFilter.PageCount = scope.tableParams.count();
var config =
{
headers:
{
Authorization: 'Bearer ' + response.RequestToken
},
params: scope.RequestFilter
}
http.get(CompaniesView.Companies_api_urls.loadCompaniesRequest, config)
.then(
function (response)
{
scope.RequestFilter.TotalCompanies = response.data.companieslistTotal;
scope.TableParams.data = response.data.companies;
if (callback != null) callback();
},
function (data, status, headers, config) //error
{
scope.IsLoading = false;
}
);
}
else
{
scope.request.requestToken = null;
//CreateExpenseView.PrepareRESTResults(data.RestResults);
}
}
else
{
scope.request.requestToken = null;
scope.request.error = data.replace(/\"/g, "");
}
});
}
Ok; finally found what was wrong. The ng-table getData operation requires a promise to be returned. My function was not configured as a deferred promise and once I realized this I put in place the required modification for my data retrieval to return a promise. One big point -- when I wrapped my call with a when - I completed with a done - and getData is operating off a then (from the promise). Once those changes were in place the pagination (total) operation worked.
Peter

angular request busy interceptor

UPDATE
I think I solved it myself. Check my interceptor that I posted as a solution below.
ORIGINAL
I was wondering if it would be possible to write an http interceptor that can let the caller know how the request is doing.
Right now, when I want to call my backend, I wrap an $http call in a wrapper that sets attributes on an object I pass it:
publ.wrap = function(f, ctrl){
ctrl.busy = true;
ctrl.error = false;
return f()
.then(function(res){
ctrl.busy = false;
ctrl.result = res;
return res;
}).catch(function(err){
ctrl.busy = false;
ctrl.error = err;
ctrl.result = undefined;
})
};
publ.login = function(args, ctrl){
publ.wrap(function(){
return $http.post('http://localhost:3001/authenticate', {
username : args.username,
password : args.password
}).then(function(jwt){
$cookies.put('token', jwt);
})
}, ctrl);
};
In this case, I call login(authArgs, $scope.loginCtrl) in my login page controller. Then I use loginCtrl.busy, loginCtrl.result & loginCtrl.error in my login template.
I pretty much want every call I make to the backend to set these attributes and make them available to the views that initiate the request.
Using a wrapper function like this gets the job done, but I'm wondering if it can be done using an interceptor? It feels to me like that would provide a much cleaner request flow that doesn't require me to explicitly wrap all of my backend calls in my services.
Now I read up on httpInterceptors, and can't seem to find a way to have them set attributes on a user-provided object. The closes thing I found was this article that has an example ( Timestamp Marker (request and response interceptors) ) where they add attributes to the config object in both the request and response interceptor stages.They don't show how to access the config object inside the responseError stage or in the caller controller.
Any help would be greatly appreciated :)
I use Angular events to handle stuff like this- for example:
.controller('parentCtrl', function($scope,$rootScope) {
$rootScope.$on('loading',function(e,_statusObj.loading) {
$scope.loading = _statusObj.loading;
if(!!_statusObj.msg) {
alert(_statusObj.msg);
}
});
})
.controller('childCtrl', function($scope,$http) {
$scope.myAjaxCall = function(_url,_data) {
$scope.$emit('loading',{ loading: true});
$http.post(_url,_data).success(function(_response) {
$scope.$emit('loading',{ loading: false });
})
.error(function(_error) {
$scope.$emit('loading',{
loading : false,
msg : _error.message
});
});
}
});
I managed to get the interceptor working. Apparently we CAN access the config file in all interceptor phases:
/******************************************
SETUP BUSY/ERROR/DATA HTTP INTERCEPTOR
*******************************************/
.config(function($httpProvider){
$httpProvider.interceptors.push(function($q) {
return {
request : function(config) {
if(config.ctrl){
config.ctrl.busy = true;
config.ctrl.error = false;
config.ctrl.data = undefined;
}
return config;
},
response : function(response) {
if(response.config && response.config.ctrl){
response.config.ctrl.busy = false;
response.config.ctrl.data = response.data;
}
return response;
},
responseError : function(response){
// note: maybe use a different error message for different kinds of responses?
var error = response.status + " "+response.statusText+" - "+response.data;
if(response.config && response.config.ctrl){
response.config.ctrl.busy = false;
response.config.ctrl.error = error;
}
return $q.reject(error);
}
};
});
})

$httpBackend return promise value issue

I want to implement a login function using AngularJS and my backend is in Rails. i decided to implement it using the $httpBackend but I have a problem.
When it gets into the $httpBackend function, it does update the token with the latest token from the database but i need to return the value to my services of which that doesnt seem to be happening. I know this has to do with promise and deferred etc but i am not well conversant with those.
SO this is my code
var authorized = false;
var token;
$httpBackend.whenPOST('https://login').respond(function(method, url, data) {
var loginDetails = data;
var d= $q.defer();
function startToken(loginDetails) {
getTokens.newToken(loginDetails).then(function(result) {
if(result.length > 0) {
var updateDB = "UPDATE preferences SET value='"+result[0].token+"' WHERE description='token'";
$cordovaSQLite.execute(db, updateDB).then(function(res) {
var updateDB1 = "UPDATE preferences SET value='true' WHERE description='logged_in'";
$cordovaSQLite.execute(db, updateDB1).then(function(res) {
var query = "SELECT description, value FROM preferences";
$cordovaSQLite.execute(db, query).then(function(res) {
if(res.rows.length > 0) {
if(res.rows.item(3).value!=null || res.rows.item(3).value!='') {
getTokens.getCRMToken(res.rows.item(2).value).then(function(resulttoken){
if(resulttoken[0].token == res.rows.item(3).value) {
token = res.rows.item(3).value;
}
d.resolve(token)
});
}
} else {
console.log("No results found");
}
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}
else {
console.log("reject")
d.reject(result);
}
}, 1000);
return d.promise;
}
var a = startToken(loginDetails).then(function(token) {
// in here the value for token is correct i then go ahead to set the value for authorized and resolve it
console.log(token)
if(token.length > 0){
console.log("authorized true")
authorized = true;
d.resolve(token, authorized)
}
else
{
console.log("authorized false")
authorized = false;
d.reject(token, authorized)
}
return d.promise;
})
// this is where i have my issue. all i want to do is to just check if the value for authorized is true, if yes, return the value for token.
//authorized = true;
//return [200 , { authorizationToken: token }];
});
Complete rewrite
Sadly, I think the short answer is that you cannot use promises with $httpBackend. See this discussion: https://github.com/angular/angular.js/issues/11245
In my original answer, I didn't recognize that you were using the $httpBackend mock as I merely concentrated on your incorrect promise code. The information on promises (https://docs.angularjs.org/api/ng/service/$q) is still valid.
The unit test version of ngMock does not handle promises. If you are using a version of $httpBackend which can handle promises, you need to return the promise, not the [200, "status"].
However, that said, in your rewrite, you also reuse the same promise after it has been resolved which is incorrect. You can only resolve or reject a defer once. So you need to either chain your then() functions or create a new defer. Also, you didn't actually need to create a defer since the newToken() function actually returns a promise.

HTTPInterceptor in AngularJS

We have APIs which returns errors inside the reponse XMLs instead of rejecting them and sending error responses.
So I have the following code to handle them,
$http({
url: apiURL,
method: "POST",
data: req,
headers: oHeaders,
dataType: "xml",
})
.success(function(data,status) {
console.log('success!!!!!');
deff.resolve(data);
})
.error(function(data,status) {
console.log('This is what I want to see!!!!!');
deff.reject(data);
});
myApp.factory('customHttpInterceptor', ['$q', function ($q) {
return function (promise) {
return promise.then(function (response) {
var parsed;
if (response.config.dataType == 'xml'){
parsed = $.xml2json(response.data);
if (parsed) {
angular.forEach(parsed, function(v, k) {
if (k == 'status') {
if (v.APIErrors) {
return $q.reject(response);
}
}
});
}
console.log(parsed);
}
return response;
}, function (errResponse) {
// do something on error
console.log('Error section in interceptor');
return $q.reject(errResponse);
});
};
}]);
What I expected to get from is that when an error is identified within the interceptor it will reject the promise and the control would get into the error section of the $http and log the "This is what I want to see!!!!!" message. But instead is show the "success!!!!!" message.
Is this the way it works or am I doing something wrong?
Ish
I see several things that seem wrong here :
Returning a result in forEach
angular.forEach(parsed, function(v, k) {
if (k == 'status') {
if (v.APIErrors) {
return $q.reject(response);
}
}
});
You probably wrote this because you're used to put return statements inside for loops. This does not work here, because you're returning from inside a function body. There is never any reason to return something in a forEach body function: this value is not going to be used by anything.
Angular Interceptor API
Maybe you just didn't show it in your code, but you have to register your interceptor.
$httpProvider.interceptors.push('customHttpInterceptor');
What's more, your interceptor does not have the correct form : in your case, it should be a object with a response method that returns a promise of a response from a response.

Chaining Angular Promises

I am having some trouble chaining promises in Angular. What I want to do is fetch my project object from the API, then check if the project owner has any containers, if they do, trigger the another GET to retrieve the container. In the end the container assigned to scope should either be null or the object retrieved from the API.
Right now, this example below resolves immediately to the second then function, and I get the error, TypeError: Cannot read property 'owner' of undefined. What am I doing wrong?
$http.get('/api/projects/' + id).then(function (data) {
$scope.project = data.project;
return data.project;
}).then(function (project) {
var containers = project.owner.containers;
if (containers.length) {
return $http.get('/api/containers/' + containers[0]);
} else {
return null
}
}).then(function (container) {
$scope.container = container;
});
Ah, turns out the data from passed into then is inside a field, so I needed to do
$scope.project = data.data.project;
return data.data.project;
Your example code works, but what if the $http call fails because of a 404? Or you want to later want to add some extra business logic?
In general you want to handle 'negative' cases using a rejecting promise, to have more control over the chaining flow.
$http.get('/api/projects/' + id).then(function (data) {
$scope.project = data.data.project;
return data.data.project;
}).then(function (project) {
var containers = project.owner.containers;
if (containers.length) {
return $q.reject('containers empty');
}
return $http.get('/api/containers/' + containers[0]);
}).then(function (container) {
$scope.container = container;
}).except(function (response) {
console.log(response); // 'containers empty' or $http response object
$scope.container = null;
});

Resources