Angular service How to chain promises in this scenario? - angularjs

I have an angular service that talks to a restful webservice.
The restful service has the following methods
getManifest
login
serviceMethod1
serviceMethod2
...
The getManifest returns a manifest structure and the login returns an authentication token; both are required to make other serviceMethodX calls but both manifest and auth token needs to be requested only once, meaning they can be requested part of the first serviceMethodX call or can be prefetched before any call but need not be called again for subsequent calls.
I have the following service
'use strict';
myApp.factory('MyService', function($http, $q, ManifestService){
var manifest = null;
var authenticationToken = null;
return {
getAuthenticationToken: function() {
var deferred = $q.defer();
$http.post('/login', authData )
.success(function(data, status, headers, config){
authenticationToken = data.authToken;
deferred.resolve(data.authToken);
});
return deferred.promise;
},
getManifest: function() {
var deferred = $q.defer();
$http.post('/manifest', authData )
.success(function(data, status, headers, config){
deferred.resolve(data.manifest);
manifest = data.manifest;
});
return deferred.promise;
},
makeServiceCall: function(url, data) {
var deferred = $q.defer();
// use manifest and authtoken here
$http.post(url, authData )
.success(function(data, status, headers, config){
deferred.resolve(data);
});
return deferred.promise;
}
}
});
How can I guarantee manifest and authToken are populated before makeService function is called or how an I chain the promises if they are not populated, just the first time?
Thanks

You could have something like a reqsDeferred that keeps the service calls pending until it is resolved. login and getManifest each check if the other resolved before them, and resolve reqsDeferred when so:
'use strict';
myApp.factory('MyService', function($http, $q, ManifestService){
var manifest = null;
var authenticationToken = null;
// [1] Create the deferred requirements object
reqsDeferred = $q.defer();
return {
getAuthenticationToken: function() {
var deferred = $q.defer();
$http.post('/login', authData )
.success(function(data, status, headers, config){
authenticationToken = data.authToken;
deferred.resolve(data.authToken);
//[2] Check for the other to be resolved
if (manifest !== null) {
reqsDeferred.resolve();
}
});
return deferred.promise;
},
getManifest: function() {
var deferred = $q.defer();
$http.post('/manifest', authData )
.success(function(data, status, headers, config){
deferred.resolve(data.manifest);
manifest = data.manifest;
//[2] Check for the other to be resolved
if (authenticationToken !== null) {
reqsDeferred.resolve();
}
});
return deferred.promise;
},
makeServiceCall: function(url, data) {
var deferred = $q.defer();
//[3] Keep the calls pending until the promise is satisfied
reqsDeferred.promise.then( function () {
// use manifest and authtoken here
$http.post(url, authData )
.success(function(data, status, headers, config){
deferred.resolve(data);
});
});
return deferred.promise;
}
}
});

Related

deferred.resolve giving error TypeError: undefined is not a function

I am using angularjs and cordova tool for creating application.
I have created service, which contains code for calling APIs. and in that I want to return response to my angular controller.
My code is,
Service,
JodoModule.factory('commonServices', function ($http, $q, $rootScope) {
return {
getServiceData: function (url) {
$rootScope.loading = true;
var deferred = $q.defer();
var req = {
method: 'GET',
url: url
}
$http(req).success(function (data) {
alert("data in service = " + JSON.stringify(data.Data));
deferred.resolve(data);
}).error(function (data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
}
};
});
My controller is,
commonServices.getServiceData("My url").
success(function (data, status, headers, config) {
alert(data);
}).
error(function (data, status, headers, config) {
alert("Got error");
});
In above code, in service, its showing elert message for JSON.stringify(data.Data)); in success block, so data is comming, but its not executing deferred.resolve(data); properly...
in Web tool bar its giving me error,
ie. TypeError: undefined is not a function
My o/p is :
{"status":"SUCCESS","Message":"success","Token":"","Data":[{"Id":17,"UserId":"477f1919-6b80-4804-a325-ac0cb05bcd3e","UserName":"honey","FirstName":"honey","LastName":null,"ProfilePic":false,"Status":2}]}
How can I solve this error. ?
Ordinary $q promises don't have a .success() or .error() method, but you shouldn't be using the deferred antipattern anyway. Instead, do this:
JodoModule.factory('commonServices', function ($http, $rootScope) {
return {
getServiceData: function (url) {
$rootScope.loading = true;
var req = {
method: 'GET',
url: url
};
return $http(req).then(function (result) {
alert("data in service = " + JSON.stringify(result.data.Data));
return result.data;
});
}
};
});
Controller:
commonServices.getServiceData("My url").
then(function (data) {
alert(data);
}).
catch(function (result) {
alert("Got error");
});
Quite a bit cleaner, ay?

Angular - Make multiple request on app.run()

On my app, I need to recover data (json) by making multiples validations using http requests before all my app starts. So my problem is that I'm using angular.run() to make all the http requests and resolving all of the validations with promises.
The problem is, not all of my promises are executed before my app is started.
part of my code is:
appModule.run(configRun);
configRun.$inject = [
'$http', '$rootScope', 'gettextCatalog', 'ipLoadDataService',
'webStorageService', 'ipDataSetParserService'];
function configRun($http, $rootScope, gettextCatalog, ipLoadDataSrv, webStrSrv, dataSetParser) {
webStrSrv.clear();
ipLoadDataSrv.getHeadDataSet2()
.then(function (responseHead) {
if (ipLoadDataSrv.updatedDataSet2(responseHead.headers["last-modified"])) {
//save into localstorage
webStrSrv.clear();
webStrSrv.setItem("last-modified", { date: responseHead.headers["last-modified"] });
ipLoadDataSrv.getDataSet2()
.then(function (responseData) {
$rootScope.cabecera = responseData;
})
}
})
}
// LoadDataService
appModule.factory('ipLoadDataService', loadDataService);
loadDataService.$inject = ['$http',
'$q',
'webStorageService',
'myPrjEnvironment',
'ipDataSetParserService'];
function loadDataService($http, $q, webStoreService, myPrj, dataSetParser) {
var eventMap = [];
var ip_loadDataService = {
getHeadDataSet2: getHeadDataSet2,
requestDataSet: requestDataSet,
updatedDataSet2: updatedDataSet2,
getDataSet2: getDataSet2
};
return ip_loadDataService;
function getHeadDataSet2() {
/*HEAD*/
var deferred = $q.defer();
$http.head(myPrj.URL_DATA)
.success(function (data, status, headers, config) {
var response = [];
response.data = data;
response.headers = headers();
deferred.resolve(response);
//return deferred.promise;
}).error(function (data, status) {
deferred.reject(data);
});
return deferred.promise;
}
function getDataSet2() {
return xhr('get', [myPrj.URL_DATA]);
}
function updatedDataSet2(last_date_modified) {
//var self = this;
var dateOnWebStore = webStoreService.getItem("last-modified");
if (dateOnWebStore === null || Date.parse(dateOnWebStore.date) < Date.parse(last_date_modified))
return true;
return false;
}
function xhr(type, config) {
if (!config && angular.isArray(type)) {
config = type;
type = 'get';
}
var deferred = $q.defer();
$http[type].apply($http, config)
.success(function (data, status, headers, config) {
var response = [];
response.data = data;
response.headers = headers();
deferred.resolve(response);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
Answering the question in your second post, maybe you better edit your original post with the new issue you encountered.
If what you are looking for is a way to activate a state (home.myPrjMain in your case) you can do this in various ways:
Using JS - use $state.go(). See - $State documentation
Using directive - use the ui-sref directive with the name of the required state. See - ui-sref documentation
Using regular html href (Navigate to url) - with the full address of the state you need. In your case, "/main".
I hope this was helpful
Have the UI start in an initial loading state then use ui-router to wait for the various pieces to resolve before going to the initial state.
Here is a fiddle showing how it works. Fiddle
I did two parts, one with a single fake service call using timeout and a second with a chained set of calls,.
this.slowServiceCall = function(input, delay) {
var deferred = $q.defer();
var workFinished = function () {
deferred.resolve(input);
};
$timeout(workFinished, delay);
return deferred.promise;
};
this.slowChainedServiceCall = function(input, delay) {
var deferred = $q.defer();
var workFinished = function () {
deferred.resolve(input);
};
$timeout(workFinished, delay);
var promiseChain = deferred.promise.then(function(result) {
var deferred2 = $q.defer();
$timeout(function(){
deferred2.resolve(result + ' Second Piece');
},100);
return deferred2.promise;
});
return promiseChain;
};

Why does an angular $http call that fails repeat itself infinitely?

I have:
this.isAuthenticated = function() {
var deferred;
deferred = $q.defer();
$http({
url: "" + endpoints.authService + "api/v1/session/check",
method: 'GET',
withCredentials: true
}).success(function(response) {
var user;
if (response.authenticated === true) {
user = response;
}
return deferred.resolve(response);
}).error(function(data, status, headers, config) {
deferred.reject(data);
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');
});
return deferred.promise;
};
Assuming that the endpoint is down, apparently it tries to do the call infinitely. Is this some known Angular behavior? And can I disable it?
No factory making http call when your controller invoking that call please see here: http://plnkr.co/edit/u7YSD8gkbOSKN32SU62P?p=preview. It's rather you controller keeping call factory function.
var app = angular.module('plunker', []);
app.factory('dataService', function($http, $q, $rootScope) {
var endpoints = {
authService: "ttest"
};
this.isAuthenticated = function() {
var deferred;
deferred = $q.defer();
$http({
url: "" + endpoints.authService + "api/v1/session/check",
method: 'GET',
withCredentials: true
}).success(function(response) {
var user;
if (response.authenticated === true) {
user = response;
}
return deferred.resolve(response);
}).error(function(data, status, headers, config) {
deferred.reject(data);
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');
});
return deferred.promise;
};
return this;
});
app.controller('MainCtrl', function($scope, dataService) {
$scope.$on('api-error', function(a, b){
alert(b);
});
dataService.isAuthenticated();
});
just put this deferred.resolve(response); instead of return deferred.resolve(response);
and also remove return from :
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');

Angular ui-router get asynchronous data with resolve and promise/deferred

I've having a problem to get the resolve mechanism working in my application.
I separated the webservice call into an extra module and using deferred/promise to have callbacks.
Before showing the state "workflowdefinitions.detail", the app should load the workflow definition be using the workflowDefinitionId of the $stateParams and call the function "getWorkflowDefinition" of the workflowDefinitionService at the service module.
I tried out multiple things that I had read here, but can't get it working. How do I need to handle the returned promise to pass the return data to the workflowDefinition defined by resolve?
Can this work with my services or do I have to define the service in a different way?
app.js
var atpApp = angular.module('atpApp', [ 'ui.router', 'workflowServices', 'workflowControllers' ]);
atpApp.config([ '$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider , $locationProvider) {
$urlRouterProvider.otherwise('/workflowdefinitions');
$stateProvider.state('workflowdefinitions', {
url : '/workflowdefinitions',
controller : 'WorkflowDefinitionListCtrl',
templateUrl : 'partials/workflowdefinition-list.html'
})
.state('workflowdefinitions.detail', {
url : '/:workflowDefinitionId',
views : {
'#' : {
templateUrl : 'partials/workflowdefinition-detail.html',
controller : 'WorkflowDefinitionDetailCtrl',
resolve: {
workflowDefinition: function($stateParams, workflowDefinitionService) {
return workflowDefinitionService.getWorkflowDefinition($stateParams.workflowDefinitionId);
}
}
}
}
});
} ]);
atpApp.run([ '$rootScope', '$state', '$stateParams', function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
} ]);
Module for Services (workflowSevices.js)
var workflowServices = angular.module('workflowServices', []);
workflowServices.service('workflowDefinitionService', function($http, $q) {
var config = {headers: {
'Accept': 'application/json'
}
};
this.getWorkflowDefinitions = function(){
var deferred = $q.defer();
$http.get('http://localhost:8080/vms-atp-webapp/services/rest/workflows', config).
success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
};
this.getWorkflowDefinition = function(workflowDefinitionId){
var deferred = $q.defer();
$http.get('http://localhost:8080/vms-atp-webapp/services/rest/workflows/'+workflowDefinitionId, config).
success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
};
this.activateWorkflowDefinition = function(workflowDefinitionId){
var deferred = $q.defer();
$http.post('http://localhost:8080/vms-atp-webapp/services/rest/workflows/'+workflowDefinitionId+"/activate", config).
success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
};
this.deactivateWorkflowDefinition = function(workflowDefinitionId){
var deferred = $q.defer();
$http.post('http://localhost:8080/vms-atp-webapp/services/rest/workflows/'+workflowDefinitionId+"/suspend", config).
success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
};
});
This concept should be working. There is a plunker, which should be doing almost the same you've tried above. No changes, as is. (as is in the code above)
The only change - for example purposes - is the service method getWorkflowDefinition, which does delay because of $timeout service, but then returns the param passed
this.getWorkflowDefinition = function(param){
var deferred = $q.defer();
$timeout(function(){
deferred.resolve(param);
}, 750)
return deferred.promise;
};
So, your concept, design is working, check more here: plunker
Additionally you don't need the boiler plate for deferred/resolve everywhere.
This code
var deferred = $q.defer();
$http.post( 'http://localhost:8080/vms-atp-webapp/services/rest/workflows/' + workflowDefinitionId +"/suspend", config).
success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
can be simplified to
simple
return $http.get('http://localhost:8080/vms-atp-webapp/services/rest/workflows', config);
This is because the $http.get returns a promise which when fulfilled is internally resolved/rejected on success and error.

ngTagsInput not populating from angular $http

Im a complete angularjs newbie. So hopefully I am somewhat on track.
I have a datacontext configured like
(function () {
'use strict';
var serviceId = 'datacontext';
angular.module('app').factory(serviceId, ['common', '$http', datacontext]);
function datacontext(common, $http) {
var $q = common.$q;
var service = {
getCustomerGroups: getCustomerGroups
};
return service;
function getCustomerGroups() {
var groups = [];
$http({ method: 'GET', url: '/api/getgroups' }).
success(function (data, status, headers, config) {
console.log(status);
console.log(headers);
console.log(data);
groups = data;
return $q.when(groups);
}).
error(function (data, status, headers, config) {
console.log(data);
// called asynchronously if an error occurs
// or server returns response with an error status.
});
return $q.when(groups);
}
}
})();
Within my view I am using ngTagsInput
<tags-input ng-model="groups"
display-property="GroupName"
placeholder="Add Customer Group"
enableeditinglasttag="false"
class="ui-tags-input"
replace-spaces-with-dashes="false">
</tags-input>
And finally my controller
(function () {
'use strict';
var controllerId = 'customers';
angular.module('app').controller(controllerId, ['common','$scope','$http','datacontext', customers]);
function customers(common,$scope,$http,datacontext) {
var vm = this;
vm.title = 'Customers';
$scope.groups = [];
function getGroups() {
return datacontext.getCustomerGroups().then(function (data) {
return $scope.groups = data;
});
}
activate();
function activate() {
var promises = [getGroups()];
common.activateController(promises, controllerId)
.then(function() {
}
);
}
}
})();
I am not getting any errors and I can see the correct data is returned in the success method of $http. However the tag is not populated. Is it because the tag is calling the datasource before the $http has completed?
I am not sure how $q.when works, but it returns promise but does not resolve it. You should us the defer api.
So at start set
var defer = common.$q.defer();
and later in success do defer.resolve.
success(function (data, status, headers, config) {
console.log(status);
console.log(headers);
console.log(data);
groups = data;
defer.resolve(data);
and see if it works.

Resources