I try to assign a property of a service object by using the $http but I have confusing results. Why this doesn't work (here is my code):
.service('config', function ($http) {
var config = {
get_host: function () {
if (online == 0) {
return offlineHost;
}
return onlineHost;
},
online: 'false',
host: 'false',
checkConnection: function () {
//this wont work;
/*
$http.get(this.host + http_url ).then(function(response) {
return response.data.ping;
});
*/
//this will work
return 'oke';
},
_load: function () {
this.host = this.get_host();
this.online = this.checkConnection();
this.url_api = this.host + http_url;
if (this.online == 1) {
this.offline_message = 'Maaf aplikasi tidak bisa terkoneksi dengan server atau anda offline';
}
}
};
//run constructor and get value;
config._load();
return config;
}) //end config class
In my controller :
var online = config.online;
alert(online) //return undefined, but the $http request on firebug is showed return value
service:
.service('config', function ($http, $q) {
var config = {
get_host: function () {
if (online == 0) {
return offlineHost;
}
return onlineHost;
},
online: 'false',
host: 'false',
checkConnection: function () {
var deferred = $q.defer();
$http.get(this.host + http_url ).then(function(response) {
$q.resolve(response.data.ping);
});
return $q.promise;
},
_load: function () {
this.host = this.get_host();
this.online = this.checkConnection();
this.url_api = this.host + http_url;
if (this.online == 1) {
this.offline_message = 'Maaf aplikasi tidak bisa terkoneksi dengan server atau anda offline';
}
}
};
//run constructor and get value;
config._load();
return config;
}) //end config class
controller:
config.online.then(function(data){
alert(data);
var online = data;
handleDate(online); // this is a predefined function to handle your data when it's downloaded
});
That's because the $http service calls are asynchronous.
The easiest way to handle this is to make your service return a promise:
return $http.get(this.host + http_url);
(return in front of $http is crucial here). Then anywhere in the code:
config.checkConnection().then(function(response) {
// there you get the response.data.ping
});
See your modified and simplified code here: http://plnkr.co/edit/cCHXCLCG3xJUwAPPlJ3x?p=preview
You can read more about that:
http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/
http://lennybacon.com/post/2014/03/21/chaining-asynchronous-javascript-calls-with-angular-js
https://docs.angularjs.org/api/ng/service/$q
Related
I am getting the error as modifyProduct.then is not a function, I have read through some article and it says because I have not returned any promises, How I can achieve this, Can someone help me
Here I am calling modifyProduct inside executionFromCompany function and then I am using executionFromCompany inside the controller
var app = angular.module('myApp', ["chart.js"]);
app.factory('ProductsService', function($http) {
function getProduct() {
return $http.get('finalmsodetails.json').then(function(response) {
//console.log(response.data);
return response.data;
});
}
function modifyProduct() {
return getProduct().then(function(rawData) {
newtest = rawData;
//console.log('test', newtest.length);
var lightData = rawData.map(function(item) {
// use Object.assign to prevent mutating original object
var newItem = Object.assign({}, item);
var lightExecutions = item.executions.map(function(d) {
var ld = {
id: d.id,
orderId: d.orderId,
executionStatus: d.executionStatus,
executedOn: d.executedOn,
executedBy: d.executedBy,
executedByDisplay: d.executedByDisplay,
};
return ld;
});
newItem.executions = lightExecutions;
return newItem;
});
return lightData;
});
}
function executionFromCompany() {
return modifyProduct.then(function(lightData) {
executionByCompany = $filter('filter')(lightData.executions, function(inputs) {
if ((inputs.executedBy == 'a')) return inputs;
});
console.log(executionByCompany);
return executionByCompany;
});
}
return {
getProduct: getProduct,
modifyProduct: modifyProduct,
executionFromCompany: executionFromCompany
};
});
app.controller('MainCtrl', function($scope, ProductsService) {
ProductsService.executionFromCompany().then(function(value) {
console.log(value);
}, function(err) {
// Here will be if there was an error
})
});
modifyProduct is a function, not an object
change this
modifyProduct.then
to this
modifyProduct().then
I have this script in my app.js:
app.run(['$http', '$location', 'myAppConfig', function ($http, $location, myAppConfig) {
if (myAppConfig.webAPIPath.main == '') {
var getconfigDone = false;
$http.get('fileHandler.ashx?action=getconfig')
.then(function (result) {
if (JSON.parse(result.data.Data).APIURL !== undefined && JSON.parse(result.data.Data).APIURL != '') {
var apiURL = JSON.parse(result.data.Data).APIURL;
if (apiURL.lastIndexOf('/') + 1 == apiURL.length) {
apiURL = apiURL.substring(0, apiURL.lastIndexOf('/'))
}
myAppConfig.webAPIPath.main = apiURL + "/";
myAppConfig.webAPIPath.account = myAppConfig.webAPIPath.main + '/api/OnlineApplicationPortal/v1/Account/';
myAppConfig.webAPIPath.dashboard = myAppConfig.webAPIPath.main + '/OnlineApplicationPortal/v1/Dashboard/';
}
else {
$location.path('Action/Welcome/apiUrlError');
}
//debugger
getconfigDone = true;
}, function (response) { debugger }
);
}
}]);
Also I have got this factory object which uses the myAppConfig in app.js:
(function () {
angular
.module('app.data')
.factory('accountDS', ['$http', '$routeParams', 'myAppConfig', function ($http, $routeParams, myAppConfig) {
var pathPrefix = myAppConfig.webAPIPath.account;
var createAccount = function (account, email) {
var OnlineApplicationPortalModel = {
Name: account.firstName,
Surname: account.lastName,
Email: email,
Password: account.password
};
return $http.post(pathPrefix + 'CreateAccount', OnlineApplicationPortalModel)
.then(function (response) {
return response;
});
};
var confirmEmail = function () {
var data = {
guid: $routeParams.guid
};
return $http.post(pathPrefix + 'ConfirmEmail', data)
.then(function (response) {
return response;
});
}
return {
createAccount: createAccount,
confirmEmail: confirmEmail
};
}]);
})();
The service object needs to use myAppConfig.webAPIPath.account which is resolved in the function in app.js run function. Now the problem is sometimes the browser reaches the service code sooner than than the AJAX call is returned, a race condition. I know that it is not possible in AngularJS to make a sync AJAX call. So how can I solve this?
If I correctly understand you, you want to myAppConfig.webAPIPath.account resolve this value to use it later in your code, but ajax call which provides you value for this variable is not always called before assignment. I think you could use https://docs.angularjs.org/api/ng/service/$q to solve your problem. Your code in myAppConfig should be inside function, so you can call it inside your factory and return deferred object, which then when your .account variable is set should call code from accountDS factory.
I have the following code:
.service('loginModal', function($rootScope, $uibModal) {
function updateUserData(user, data) {
Object.keys(data).forEach(function(key) {
user.facebook[key] = data[key];
});
return user.$update();
}
return function() {
var instance = $uibModal.open({
templateUrl: 'tpls/modals/login.html',
controller: function($scope, $uibModalInstance, facebookService, UserService) {
function updateUserData(user, data) {
Object.keys(data).forEach(function(key) {
user.facebook[key] = data[key];
});
return user.$update();
}
$scope.login = function() {
facebookService.login().then(function(response) {
var authResponse = facebookService.getAuthResponse();
facebookService.api('/me').then(function(response) {
if (response && !response.error) {
response.picture = 'http://graph.facebook.com/' + response.id + '/picture?type=large';
UserService.query({
'facebook.id': response.id,
'facebook.token': authResponse.accessToken
}).$promise.then(function(results) {
response.token = {
value: authResponse.accessToken,
expiresIn: authResponse.expiresIn
};
if (results.length > 0)
updateUserData(results[0], response) //THIS DOES NOT FULFILL OR REJECT
.then($uibModalInstance.close, $uibModalInstance.dismiss);
else
UserService.save({
facebook: response,
local: {
username: response.email || response.id,
password: response.token.value
}
}).$promise
.then($uibModalInstance.close, $uibModalInstance.dismiss);
}, $uibModalInstance.dismiss);
} else {
$uibModalInstance.dismiss(response.error || response);
}
}, $uibModalInstance.dismiss);
}, $uibModalInstance.dismiss);
};
}
instance.opened.then(function() {
$rootScope.openModals.push(instance);
});
function removeInstanceFromModalList() {
$rootScope.openModals.splice($rootScope.openModals.indexOf(instance), 1);
}
instance.result.then(removeInstanceFromModalList, removeInstanceFromModalList);
return instance.result;
}
Basically I'm calling loginModal().then(function(user){...},function(e){...}); from wherever I want.
The part which does not work however is right after I query UserService
if (results.length > 0)
updateUserData(results[0], response) //THIS DOES NOT FULFILL OR REJECT
.then($uibModalInstance.close, $uibModalInstance.dismiss);
I've also tried debugging like this:
updateUserData(results[0], response)
.then(function(usr) {
$uibModalInstance.close(usr); //debugger reaches this statement,
//nothing happens
}, function(e) {
$uibModalInstance.dismiss(e);
});
What's wrong with my code? only backdrop clicks seem to close the dialog.
You can use the promise returned by $uibModal.open() which has the close() method attached.
You could store it in the controller $scope:
$scope.modal_instance = $uibModal.open({ ...
and then use:
$scope.modal_instance.close();
instead of $uibModalInstance.close.
Dangit - had a version issue.
Apperantly the version of angular-ui I was using was incompatible with angular#1.4.7 so I had to upgrade to 1.4.8.
I wrote a page that allows me to change my password. The code works and it does everything I want it to do, so I started writing tests. Since I'm not as experienced in Angular testing this had proven to be quite difficult and I can't get passed this error:
TypeError: 'undefined' is not an object (evaluating 'plan.apply')
at /Users/denniegrondelaers/asadventure/myproject-web/src/users/controllers/userPasswordController.js:9
at /Users/denniegrondelaers/asadventure/myproject-web/test/unitTests/specs/users/controllers/userPasswordControllerSpec.js:98
The controller:
userPasswordController.js
users.controllers.controller('userPasswordController',
['$scope', 'Session', '$state', 'UserService', 'languages',
function ($scope, Session, $state, UserService, languages) {
$scope.languages = languages;
$scope.password = "";
$scope.notEqual = false;
$scope.isSuccessful = false;
$scope.changePassword = function() {
var pw = {
userId: Session.getCurrentSession().userId,
oldPassword: encrypt($scope.password.oldPassword),
newPassword: encrypt($scope.password.newPassword),
newPasswordRepeat: encrypt($scope.password.newPasswordRepeat)
};
if (pw.newPassword === pw.newPasswordRepeat) {
$scope.notEqual = false;
UserService.setNewPassword(pw).then(function(res) {
$scope.formErrors = undefined;
$scope.isSuccessful = true;
}, function (error) {
$scope.formErrors = error.data;
}
);
} else {
$scope.notEqual = true;
}
};
var encrypt = function (password) {
var encrypted = CryptoJS.md5(password);
return encrypted.toString(CryptoJS.enc.Hex);
};
}
]
);
The service:
userService.js
userService.setNewPassword = function (password) {
return $http
.put(EnvironmentConfig.endpointUrl +
"/password/change", password)
};
The test:
userPasswordControllerSpec.js
describe('Users', function () {
describe('Controllers', function () {
fdescribe('userPasswordController', function () {
var $scope,
controller,
$q,
willResolve,
mockSession,
mockState,
mockUserService,
mockLanguages;
beforeEach(function () {
module('mysite.users.controllers');
module(function ($provide) {
$provide.value('translateFilter', function (a) {
return a;
});
$provide.value('$state', function (a) {
return a;
});
});
mockSession = {
getCurrentSession: function () {
return {userId: 4};
}
};
mockState = {
params: {
id: 1
},
go: function () {
}
};
mockLanguages = {
getLanguages : function () {
var deferred = $q.defer();
deferred.resolve({
data: [{}]
});
return deferred.promise;
}
};
mockUserService = {
setNewPassword : function () {
var deferred = $q.defer();
if (willResolve) {
deferred.resolve({
data: [{}]
});
}
return deferred.promise;
}
};
inject(function (_$q_, $controller, $rootScope) {
controller = $controller;
$q = _$q_;
$scope = $rootScope.$new();
});
controller('userPasswordController', {$scope: $scope, Session: mockSession, $state: mockState,
UserService: mockUserService, languages: mockLanguages
});
willResolve = true;
});
it('should change password', function () {
spyOn(mockUserService, 'setNewPassword').and.callThrough();
spyOn(mockState, 'go').and.callThrough();
spyOn(mockSession, 'getCurrentSession').and.callFake();
expect(mockUserService.setNewPassword).not.toHaveBeenCalled();
expect($scope.isSubmitable()).not.toBeTruthy();
$scope.compareStoreSelection = function () {
return true;
};
$scope.password = {
oldPassword: "123456",
newPassword: "password",
newPasswordRepeat: "password"
};
expect($scope.isSubmitable()).toBeTruthy();
>>> $scope.changePassword(); <<< LOCATION OF ERROR, line 98
expect(mockUserService.setNewPassword).toHaveBeenCalled();
$scope.$apply();
});
});
});
});
I've marked the line that gives the code in the test.
Anybody any idea how to fix this? A colleague suggested altering my controller code, but I'd like to keep it as it is, since it seems logical that this code shouldn't be altered for testing to work, right?
Solution
Yarons' suggestion to change the mockSession.getCurrentSession.callFake to mockSession.getCurrentSession.callThrough fixed it!
I can't understand why it does not update the $scope.user_free_status when I set a user free but when I unset the parameter it works perfectly. I need to reload page in one case and not the other...
The datas fetched are stored in the localstorage.
Here is the code:
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'InitialCtrl',
resolve: {
theUserFreeStatus: function(DataService) {
return DataService.getUserFreeStatus();
}
}
})
Controller:
.controller('InitialCtrl', function($scope, $state, DataService ,FreeService, SharedService, theUserFreeStatus) {
// Showing set free but not unset or not
if (FreeService.isSetFree()) {
$scope.showSetFree = false;
$scope.showUnSetFree = true;
} else {
$scope.showSetFree = true;
$scope.showUnSetFree = true;
}
// Show the Free status set when arriving on page/app
$scope.user_free_status = theUserFreeStatus;
// Set user as Free
$scope.setFree = function(activity, tags) {
FreeService.setFree(activity, tags).success(function() {
console.log($scope.user_free_status);
$scope.user_free_status = DataService.getUserFreeStatus();
console.log($scope.user_free_status);
$scope.showSetFree = false;
$scope.showUnSetFree = true;
SharedService.goHome();
})
}
//// Free status unset
$scope.unsetFree = function() {
FreeService.unsetFree().success(function() {
$scope.user_free_status = [];
$scope.showSetFree = true;
$scope.showUnSetFree = false;
SharedService.goHome();
});
};
})
The services:
.factory('FreeService', function(WebService, $localstorage, $ionicPopup, DataService, $sanitize, CSRF_TOKEN) {
var cacheFreeStatus = function(free_status) {
$localstorage.setObject('user_free_status', free_status)
};
var uncacheFreeStatus = function() {
$localstorage.unset('user_free_status')
}
return {
setFree: function(activity, tags) {
var status = { SOME STUFF BLABLABLA };
var setFree = WebService.post('setstatus/', sanitizeStatus(status));
setFree.success(function(response) {
console.log('available' + response.flash);
cacheFreeStatus(response.status_response);
})
setFree.error(freeError)
return setFree;
},
unsetFree: function() {
var details = {OTHER STUFF};
var unsetFree = WebService.post('unsetstatus/', details);
unsetFree.success(function(response) {
console.log('unset ' + response.flash);
uncacheFreeStatus(response.status_response);
})
unsetFree.error(freeError)
return unsetFree;
},
isSetFree: function() {
return $localstorage.get('user_free_status');
}
}
})
.service('DataService', function($q, $localstorage) {
return {
activities: $localstorage.getObject('activities'),
getActivities: function() {
return this.activities;
},
user_free_status: $localstorage.getObject('user_free_status'),
getUserFreeStatus: function() {
return this.user_free_status;
}
}
})
* Local Storage Service
------------------------------------------------------*/
.factory('$localstorage', ['$window', function($window) {
return {
set: function(key, value) {
$window.localStorage[key] = value;
},
unset: function(key) {
localStorage.removeItem(key);
},
get: function(key, defaultValue) {
return $window.localStorage[key] || defaultValue;
},
setObject: function(key, value) {
$window.localStorage[key] = JSON.stringify(value);
},
getObject: function(key) {
return JSON.parse($window.localStorage[key] || '{}');
}
}
}])
When setting the user's status, the console returns that the $http call worked but an empty array for the $scope variable I try to set. Once I reload the page I can see the updates displayed. If I unset the user's status, the $scope is properly updated without need to reload the page.
The Webservice is just the $http call.
What am I missing here to have the $scope.user_free_status updated correctly without having to reload the page??
Thanks for your time!
Your data service is injected as service but you have not appended the functions to this.rather you have returned it as part of literal like u do in factory