Angular Controller Not Waiting for Service Promise to Resolve - angularjs

I have an Angular service that looks like this:
(function () {
'use strict';
var serviceId = 'currentUserService';
angular.module('app').factory(serviceId, ['common', 'datacontext', currentUserService]);
function currentUserService(common, datacontext) {
var $q = common.$q;
var getLogFn = common.logger.getLogFn;
var logError = getLogFn(serviceId, "error");
var user = {};
var service = {
user: user,
doesUserHaveFeature: doesUserHaveFeature
};
activate();
return service;
function activate() {
var promises = [getCurrentUser()];
$q.all(promises);
}
function getCurrentUser() {
var deferred = $q.defer();
datacontext.getLoginInformation().then(function (data) {
user = data;
deferred.resolve(data);
});
return deferred.promise;
}
function doesUserHaveFeature(featureName) {
debugger;
var feature = featureName.toLowerCase();
var result = _.filter(user.features, function(item) {
var featureString = item.toLowerCase();
return feature == featureString;
});
if (result) {
return true;
}
return false;
}
}
})();
The service is injected into my Controller and my controller calls the doesUserHaveFeature() method.
However, the doesUserHaveFeature method is called before the promise in the activate method is resolved. Therefore, the user variable is still an empty object.
How can I ensure that the promise in the activate method is returned before the doesUserHaveFeature method is called?
Thanks!
Jeremy

As the user data is retrieved asynchronously, your factory methods are going to have to return promises. Here's how I'd do it...
.factory('currentUserService', function(datacontext) {
var userPromise = datacontext.getLoginInformation();
return {
user: userPromise,
doesUserHaveFeature: function(featureName) {
return userPromise.then(function(user) {
return _.some(user.features, function(feature) {
return feature.toLowerCase() === featureName.toLowerCase();
});
});
}
};
});

Related

TypeError: Cannot read property 'then' of undefined at m.$scope.profile

I am trying to call a child service function in Main Controller.
But I am getting error on than in profile function.
This is my profile function inside main controller.
App.controller("globalCtrl", function($scope,$rootScope, $window, $location, $rootScope, $cookieStore, toastr, ClientService,) {
var theClient = $cookieStore.get('UserData') || {};
$scope.profile = function(theClient) {
var getCurrentClient = theClient;
var getOrganizationId = $rootScope.globalSession.OrganizationId;
if($rootScope.globalSession.UserRole == "Admin")
{
if (getOrganizationId) {
ClientService.getClient(getOrganizationId).then(function(aGetClientResponse) {
if(getClientResponse[0] == $scope.RESPONSE_CODE.CM_SUCCESS) {
$scope.myprofile = getClientResponse[1];
$location.path("/profile");
}
else {
toastr.warning($scope.USER_MESSAGE.SERVICE_NOT_AVAILABLE, '');
}
});
}
}
};
)};
This is Service of another controller whom function "getClient" I am calling in profile function.
App.factory('ClientService', function($http, $cookieStore, uuid2, API_URL, REQUEST_HEADER, RESPONSE_CODE) {
var theClient = $cookieStore.get('UserData') || {};
return {
getClient: function() {
if(theClient.OrganizationId) {
//API Call
var promise = $http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER).then(
function(aGetClientResponse) { //Success Callback
return [aGetClientResponse.data.GetClientResponse.Result.ResponseCode, aGetClientResponse.data.GetClientResponse];
},
function(aGetClientResponse) { //Error Callback
return [aGetClientResponse.status,''];
});
}
return promise;
},
setClient: function(aClient) {
theClient = aClient;
$cookieStore.put('UserData', theClient);
}
}
});
Need Help. cant figure out the Problem. Thanks in advance!
Change your service to just return a promise and handle that promise in your controller using then
The service directly returns API call, no then in service
var promise = $http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER)
Here is your service:
App.factory('ClientService', function($http, $cookieStore, uuid2, API_URL, REQUEST_HEADER, RESPONSE_CODE, $q) {
var theClient = $cookieStore.get('UserData') || {};
return {
getClient: function() {
if(theClient.OrganizationId) {
var promise = $http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER)
}
else {
var promise = $q.reject();
}
return promise;
},
setClient: function(aClient) {
theClient = aClient;
$cookieStore.put('UserData', theClient);
}
}
});
Now, the controller handles the success and failure response using then
ClientService.getClient(getOrganizationId).then(function(){},function(){})
Here is your controller, observe then
App.controller("globalCtrl", function($scope,$rootScope, $window, $location, $cookieStore, toastr, ClientService) {
var theClient = $cookieStore.get('UserData') || {};
$scope.profile = function(theClient) {
var getCurrentClient = theClient;
var getOrganizationId = $rootScope.globalSession.OrganizationId;
if($rootScope.globalSession.UserRole == "Admin")
{
if (getOrganizationId) {
ClientService.getClient(getOrganizationId).then(function(aGetClientResponse) {
var response = [aGetClientResponse.data.GetClientResponse.Result.ResponseCode, aGetClientResponse.data.GetClientResponse];
if(response[0] == $scope.RESPONSE_CODE.CM_SUCCESS) {
$scope.myprofile = response[1];
$location.path("/profile");
}
else {
toastr.warning($scope.USER_MESSAGE.SERVICE_NOT_AVAILABLE, '');
}
},
function(aGetClientResponse) { //Error Callback
return [aGetClientResponse.status,''];
});
}
}
};
)};
In getClient() the code looks like this:
if(theClient.OrganizationId) {
//API Call
var promise = ... some stuff ...;
}
return promise;
So if the condition is false you return undefined which doesn't have a .then property.
You should make sure that a function which returns a promise always returns a promise. If you want to signal that a missing id is an error then just return $q.reject(something) for an already rejected promise.
I'm not sure it this works, but instead of returning a object with the functions inside, have you tried to declare them as part of the factory object? I have some declared like this and they are working, so have a try:
App.factory('ClientService', function($http, $cookieStore, uuid2, API_URL, REQUEST_HEADER, RESPONSE_CODE) {
var theClient = $cookieStore.get('UserData') || {};
var function = {
getClient: function() {
if(theClient.OrganizationId) {
//API Call
var promise = $http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER).then(
function(aGetClientResponse) { //Success Callback
return [aGetClientResponse.data.GetClientResponse.Result.ResponseCode, aGetClientResponse.data.GetClientResponse];
},
function(aGetClientResponse) { //Error Callback
return [aGetClientResponse.status,''];
});
}
return promise;
},
setClient: function(aClient) {
theClient = aClient;
$cookieStore.put('UserData', theClient);
}
}
return function;
});
You can have a try with this too:
Define the services in the controller declaration (and remove one rootScope, its duplicated):
App.controller("globalCtrl", ['$scope','$rootScope','$window','$location','$cookieStore','toastr','ClientService', function($scope,$rootScope, $window, $location, $cookieStore, toastr, ClientService){
[...]
}]);
Try something like this to ensure that there is always a promise object available.
getClient: function() {
var deferred = $q.defer();
if(theClient.OrganizationId) {
//API Call
$http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER).then(
function(aGetClientResponse) { //Success Callback
deferred.resolve(aGetClientResponse);
},
function(aGetClientResponse) { //Error Callback
deferred.resolve(aGetClientResponse);
});
}else{
deferred.reject('Missing OrganizationId');
}
return deferred.promise;
}
In the else part you can return dummy promise with either resolve($q.resolve()) or reject($q.reject()) status.
JS :
getClient: function() {
if(theClient.OrganizationId) {
//API Call
var promise = $http.get(API_URL.GET_CLIENT+theClient.OrganizationId+"&CorrelationId="+uuid2.newuuid()+"&ContextOrganizationId=009", REQUEST_HEADER).then(
///your rest code
return promise;
}else{
return $q.reject();
}
}
With $q.reject() it will call error handler in profile call.So,add error handler callback as second parameter
ClientService.getClient(getOrganizationId).then(function(aGetClientResponse) {
//success code
},
function(){ //add it in your script
//check error here
});

How to test Controller that calls a service where service uses $http

I am trying to test a controller. The controller uses a service which is using $http to get the data from a json file (This json file is just a mock up of response returned from server)
My problem is that when I am testing the controller, it creates the controller object and even calls the service. But it doesnt call the $http mocked response. I not sure where I am going wrong. I tried looking at few examples but all of them are using $q.
My service looks like this:
(function(){
angular.module('mymodule')
.factory('MyService', MyService);
MyService.$inject = ['$http'];
function MyService($http) {
var service = {
retrieveData : retrieveData
};
return service;
function retrieveData(containerLabel){
var myGrossData = [];
var isMatchFound = false;
var myindex = containerLabel.slice(-4);
return $http.get('app/myGrossData.json').then(function(response) {
console.log('inside http retrieveData: ');
myGrossData = response.data;
var myindexExists = false;
var mydataObject = [];
var defaultdata = [];
angular.forEach(myGrossData, function (myGrossData) {
if (myindex === myGrossData.myindex) {
mydataObject = myGrossData;
isMatchFound = true;
}
if(!isMatchFound && myGrossData.myindex === '2006')
{
mydataObject = myGrossData;
}
if(myGrossData.myindex === '2006'){
defaultdata = myGrossData;
}
});
if (isMatchFound && response.status === 200)
{
return mydataObject;
}
else if(!isMatchFound && (response.status === 200 || response.status === 201)){
return defaultdata;
}
else //all other responses for success block
{
return 'Incorrect Response status: '+response.status;
}
},
function(error){
return 'Error Response: '+error.status;
}
);
}
};
})();
The controller calling it is :
(function () {
'use strict';
angular
.module('mymodule', [])
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = ['$scope', 'MyService'];
function MyCtrl($scope, MyService) {
var vm = this;
vm.datafromsomewhere = datafromsomewhere;
vm.displayData = [];
vm.disableBarCode = false;
vm.childCount = 0;
vm.headertext="Master Container Builder";
init();
function init() {
console.log('MyCtrl has been initialized!');
console.log(vm.headertext);
}
function myfunctionCalledByUI(input) {
processData(input);
}
function processData(containerLabel){
MyService.retrieveMasterContainer(containerLabel).then(function(data){
vm.displayData = data;
});
vm.disableBarCode = true;
vm.childCount = (vm.displayData.childData === undefined) ? 0: vm.displayData.childData.length;
vm.headertext="Myindex "+vm.displayData.myindex;
if ( vm.displayData.masterDataId.match(/[a-z]/i)) {
// Validation passed
vm.displayData.masterDataId ="No Shipping Label Assigned";
}
else
console.log('else: '+vm.displayData.masterDataId);
console.log('length of childData: '+vm.childCount);
}
}
})();
and finally my spec looks like this:
var expect = chai.expect;
describe('Test Controller', function () {
var rootScope, compile; MyService = {};
var $scope, $controller;
beforeEach(module('ui.router'));
beforeEach(function() {
module('mymodule');
inject(function ($rootScope, _$compile_,_$controller_) {
rootScope = $rootScope;
compile = _$compile_;
$scope = $rootScope.$new();
MyService = jasmine.createSpyObj('MyService', [
'retrieveData'
]);
$controller = _$controller_('MyCtrl', {
$scope: $scope
});
});
});
it('controller should be initialized and data should also be initialized', function() {
expect($controller).to.not.be.undefined;
expect($controller).to.not.be.null;
expect($controller.disableBarCode).to.equal(false);
expect($controller.childCount).to.equal(0);
expect($controller.headertext).to.equal("Master Container Builder");
});
it(' should process data when containerLabel is called into myfunction', function() {
$controller.handKeyed('12001');
expect(MyService.retrieveData).to.have.been.called;
expect($controller.processData).to.have.been.called;
expect($controller.disableBarCode).to.equal(true);
expect($controller.childCount).to.equal(0);
expect($controller.headertext).to.equal("Master Container Builder");
});
});
I am using following techstack if it helps:
angular 1.5
Ionic
Karma-jasmine
The code works when I run it. My issue is that when i run the test it doesnt populate the data in my vm.displayData variable. how do I make it get some data into the service. I added in some log statements and it skips it completely.
After all the test run including unrelated tests to this one, then I see the log statements from MyService. I am not sure how to approach this.
I think what you are looking for is the $httpBackend service. It will mock the request indicating the result. So, when your service hit the url, it will return what you passed to the $httpBackend configuration.
A simple example would be:
it('should list newest by category', function(){
$httpBackend
.expectGET(url)
.respond(techPosts /*YOUR MOCKED DATA*/);
$stateParams.category = 'tech';
var controller = $controller('HomeCtrl', { PostsResource: PostsResource, $stateParams: $stateParams });
controller.listNewestPosts();
$httpBackend.flush();
expect(controller.posts).toEqual(techPosts.posts);
});

How to refresh Angular $http cache?

My service populates all items using $http and a cache when the controller is activated. If I create a new item, by doing an $http.post(), what is the best way to refresh the cache?
The problem with the example below is the cached getAll call will return an outdated array:
(function () {
'use strict';
angular
.module('myapp')
.factory('items', itemsService)
.controller('Items', itemsController);
// myCache was created using angular-cache
itemsService.$inject = ['$http', 'myCache'];
function itemsService ($http, myCache) {
var service = {
getAll : function () {
return $http.get('/api/item', myCache);
},
createNew : function (item) {
return $http.post('/api/item', item);
}
};
return service;
}
itemsController.$inject = ['items'];
function itemsController (items) {
var vm = this;
vm.items = [];
vm.item = {};
activate();
function activate() {
items.getAll().then(function(response){
vm.items = response.data || [];
});
}
function createNew() {
items.createNew(vm.item).then(function(response){
vm.items.push(response.data);
});
}
}
})();
Edit #1 : Invalidating Cache
I've modified the code to invalidate the cache when a new item is created. The addition of the $q service, and manually rejecting or resolveing the calls seems very tedious and bloated.
Is there a better way?
(function () {
'use strict';
angular
.module('myapp')
.factory('items', itemsService)
.controller('Items', itemsController);
itemsService.$inject = ['$q', '$http', 'CacheFactory'];
function itemsService ($q, $http, CacheFactory) {
var _cache = CacheFactory.get('items') || CacheFactory('items');
var service = {
getItems : function(refresh) {
var d = $q.defer();
if (refresh) { _cache.invalidate(); }
$http.get('/api/item', _cache).then(function(response){
d.resolve(response.data);
}, function(err){ d.reject(err); });
return d.promise;
},
createNew : function(info){
var d = $q.defer();
$http.post('/api/item', info).then(function(response){
_cache.invalidate();
d.resolve(response.data);
}, function(err){ d.reject(err); });
return d.promise;
}
};
return service;
}
itemsController.$inject = ['items'];
function itemsController (items) {
var vm = this;
vm.items = [];
vm.item = {};
activate();
function activate() {
items.getAll().then(function(response){
vm.items = response.data || [];
});
}
function createNew() {
items.createNew(vm.item).then(function(response){
vm.items.push(response.data);
});
}
}
})();

Angularjs async requests

Controller 1
var promise = UserService.userexists(groupid);
promise.then(
},
function (response) {
}
);
Controller 2
var promise = UserService.userexists(groupid);
promise.then(
},
function (response) {
}
);
Service
app.factory("UserService", function ($q, $timeout) {
return {
userexists: function (groupid) {
var deferred = $q.defer();
//this is just to keep a pointer to parent scope from within promise scope.
IsCurrentUserMemberOfGroup(groupid, function (isCurrentUser) {
if (isCurrentUser) {
deferred.resolve(isCurrentUser);
}
else {
deferred.reject(isCurrentUser);
}
});
return deferred.promise;
}
}
});
function IsCurrentUserMemberOfGroup(groupId, OnComplete) {
var currentContext = new SP.ClientContext.get_current();
var currentWeb = currentContext.get_web();
var currentUser = currentContext.get_web().get_currentUser();
currentContext.load(currentUser);
var allGroups = currentWeb.get_siteGroups();
currentContext.load(allGroups);
var group = allGroups.getById(groupId);
currentContext.load(group);
var groupUsers = group.get_users();
currentContext.load(groupUsers);
currentContext.executeQueryAsync(OnSuccess, OnFailure);
function OnSuccess(sender, args) {
var userInGroup = false;
var groupUserEnumerator = groupUsers.getEnumerator();
while (groupUserEnumerator.moveNext()) {
var groupUser = groupUserEnumerator.get_current();
if (groupUser.get_id() == currentUser.get_id()) {
userInGroup = true;
break;
}
}
OnComplete(userInGroup);
}
function OnFailure(sender, args) {
OnComplete(false);
}
}
I have controller 1 and controller 2 on the same page, both use the same service to check the user exist in a specific group. Problem is the service runs the check user method (IsCurrentUserMemberOfGroup) for each controller. Is there a way so that I execute the method once and other controllers can use it?
This is a sharepoint 2010 environment.
Thanks
You could cache the result in your service, maybe something like
app.factory("UserService", function ($q, $timeout) {
return {
var cache = {};
userexists: function (groupid) {
var deferred = $q.defer();
if (cache[groupif]) {
deferred.resolve(cache[groupif]);
return deferred.promise;
}
IsCurrentUserMemberOfGroup(groupid, function (isCurrentUser) {
if (isCurrentUser) {
cache[groupif] = isCurrentUser;
deferred.resolve(isCurrentUser);
}
else {
deferred.reject(isCurrentUser);
}
});
return deferred.promise;
}
}
});

How to get data by service and $cacheFactory by one method

I have a factory which get data from server. In the factory method I have used $cacheFactory to cache getting data. My code is as follows..
var buyersService = function ($http, $q,$cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
BuyersFactory.GetBuyers = function () {
var dataList = buyersService.cache.get('BuyerData');
if (dataList != null && dataList.length > 0) {
return dataList;
}
else {
return $http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
return results.data;
});
}
}
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});
Now I have called GetBuyers method from controller. My method is like below..
var BuyerController = function ($scope, BuyersService) {
$scope.Buyers = [];
init();
function init() {
getBuyers();
}
function getBuyers() {
BuyersService.GetBuyers()
.then(function (data) {
$scope.Buyers = data;
}, function (error) {
alert(error.message);
});
}
};
app.register.controller('BuyersController', ['$scope', 'OMDataService', BuyerController]);
When I have executed my controller method second time I have got an error message in promise part.
Object doesn't support property or method 'then'
The issue here is that your function returns two different things: either a promise or plain data. To remedy this, use another promise to control the flow and return that one as the result of the function.
Update your code to
var buyersService = function ($http, $q,$cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
BuyersFactory.GetBuyers = function () {
var buyersDataIsAvailable = $q.defer();
var dataList = buyersService.cache.get('BuyerData');
if (dataList != null && dataList.length > 0) {
buyersDataIsAvailable.resolve(dataList);
}
else {
$http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
buyersDataIsAvailable.resolve(results.data);
});
}
return buyersDataIsAvailable.promise;
}
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});

Resources