I have an angular service that has one function visible to the controllers using the service. ie. getData
I want my service to be able to use a helper function called modifyData in the service that manipulates the data before returning it to the service which then sends the data to the controller. I do not want to use the helper function outside of the service or have it accessible outside of the service.
app.service("dataService", function() {
/* HELPER FUNCTION */
var modifyData = function(data) {
data.modified = true;
return data;
}
this.getData = function() {
//Do a http request to get oldData variable
var newData = modifyData(oldData);
return newData;
}});
My error from Angular is Error: modifyData is undefined.
What am I doing wrong here?
EDIT: Here is my actual code since my example code should work then I might have simplified it too much.
rabApp.service("reviewService",
["$http", "$q", "beerService", "userService",
function($http, $q, beerService, userService) {
/* HELPER FUNCTIONS */
/* Builds a complete review object from a user object, beer object, and partial review object */
var buildReviewObj = function(reviewObj, switchClass) {
var deferred = $q.defer();
if(switchClass) {
reviewObj.dirClass = "left";
} else {
reviewObj.dirClass = "right";
}
//Make sure the review picture is valid and if not use default one
if(reviewObj.image.length <= 0) {
reviewObj.image = "images/default-beer-pic.jpg";
}
//Make sure the review style is present if not put a default in
if(reviewObj.style.length <= 0) {
reviewObj.style = "Unknown";
}
//Add user data
userService.getUser(reviewObj.author_id).success(function(data, status) {
//Check if we have a matched user
if(data.status === "success") {
var userObj = data.data;
//Make sure the profile picture is valid and if not use default one
if(userObj.profile_pic.length <= 0) {
userObj.profile_pic = "images/default-profile-pic.jpg";
}
reviewObj.author = userObj;
} else {
deferred.reject("Bad User object returned");
}
});
//Add beer data
beerService.getBeer(reviewObj.beer_id).success(function(data, status) {
//Check if we have a matched beer
if(data.status === "success") {
var beerObj = data.data;
reviewObj.beer = beerObj;
} else {
deferred.reject("Bad Beer object returned");
}
});
deferred.resolve(reviewObj);
}
/* Gets an array of review objects from the backend
* #return Array of Review objects
*/
this.getReviews = function() {
var deferred = $q.defer();
$http({
method: "GET",
url: "/includes/services/reviews.php",
params: { a : "getReviews",
limit : "10" }
}).success(function(data, status) {
//switchClass variable alternates the review to display left or right
var switchClass = true;
//Notify review controller that we are loading reviews
deferred.notify("loading");
//Check if we have reviews
if(data.status === "success") {
var reviews = [];
//Add beer and user data to each review
data.data.forEach(function(reviewObj, index) {
buildReviewObj(reviewObj, switchClass).then(function(data) {
switchClass = !switchClass;
reviews.push(reviewObj);
});
});
//Notify review controller that we are done
deferred.notify("finished");
//Review array built successfully return reviews
deferred.resolve(reviews);
} else {
//Couldn't get reviews return false
deferred.reject("Couldn't access reviews.php back end service");
}
}).error(function() {
//Couldn't get reviews return false
deferred.reject("Couldn't access reviews.php back end service");
});
return deferred.promise;
}}]);
I found my error I was missing
return deferred.promise;
In the helper function.... facepalm this took me 10 minutes to solve after posting here but only after I struggled for an hour an half before posting here.
Related
I am new to angular and pardon my ignorance if any. I am trying to create a simple service that will do a get funtionality and serve data into array.
The problem i am having is, no mattter what i do - i always get the same data for any parameter i pass.
Here is my sample service
function myService($http, $q) {
var service = {
getSomeData: getSomeData:
};
var def = $q.defer();
return service;
function getSomeData(category) {
if (category === 'books') {
url = 'http://www.someurl1';
} else {
url = 'http://www.someurl2'
};
$http.get(url, {
params: {
'type': category
}
}).success(function(data) {
def.resolve(data);
}).error(function() {
def.reject('Failed to get data');
});
return def.promise;
}
}
})();
Once i have this, in my controller, i am trying to call it for sample purposes like this
$scope.someData = [] ;
$scope.someData.push(myService.getSomeData('DVDs');
$scope.someData.push(myService.getSomeData('books');
Now when i my look at my $scope.someData,
i have an array of two objects - the problem being that they are always the same and doesnt have data specific to books and dvds.
Another minor issue I have is my object someData has
array --> Promise -- > $$state --> value
which then has the actual data. how can i get the data directly.
i tried return def.promise(data);
One problem is you are only creating one promise outside the getSomeData function.
A promise can only be resolved once. You need a new promise for each request.
Also $http already returns a promise but you can't push it directly into an array as data.
your code should look more like:
Service:
function myService($http, $q) {
var service = {
getSomeData: getSomeData
};
return service;
function getSomeData(category) {
if (category === 'books') {
url = 'http://www.someurl1';
} else {
url = 'http://www.someurl2'
};
return $http.get(url, {
params: {
'type': category
}
}).then(function(response) {
return response.data;
}).catch(function(err) {
console.log("Ooops", err)
});
}
}
Controller
$scope.someData = [] ;
myService.getSomeData('DVDs').then(function(data){
data.forEach(function(item){
$scope.someData.push(item);
});
});
Scenario: I want to show notification to user once product is added to wishlist
I have already implemented this code & its working but I want to know the best approach of doing this using angular features. I have used $rootScope for this. Below is the code:
In Controller:
$scope.addToWish = function (product) {
var user = checkUserLoginStatus.getIsUserLoggedin();
if (user == ""){
$('#ModalLogin').modal();
}else {
DataRepository.saveProductsToWishList(product); // service
$scope.$watch($rootScope.addedToWishList, function () {
$timeout(function() {
$rootScope.addedToWishList = false;
}, 4000); // to hide alert after 4 secs
});
}
}
In DataRepository service, I am updating value on $http success function
$http({
// Some POST call
})
.success(function (response)
{
$rootScope.addedToWishList = true; // this makes ng-show true on html
$rootScope.$emit('WishList modified', response);
})
If you return the $http call from DataRepository you can chain a .then to it like so:
Controller:
$scope.addToWish = function (product) {
var user = checkUserLoginStatus.getIsUserLoggedin();
if (user == ""){
$('#ModalLogin').modal();
} else {
DataRepository.saveProductsToWishList(product)
.then(function() {
// show wishlist message
$scope.wishlistMessage = "Added to wishlist";
$timeout(function() {
// hide wishlist message
$scope.wishlistMessage = "";
}, 4000);
});
}
}
Service
function saveProductsToWishList(product) {
return $http({
// some post call
});
}
In general you want to stay clear of the rootScope.
I have a simple to-do app I'm working on, which is using Angular and then PHP/MySQL for the backend.
I now have a simple app that works, where I can add new todos, and add the "percentage completed" per day to the Database, using a simple $http post.
However now what I'm looking to do is, populate the $scope.historicalDailyPercentages array, with data from the database.
At the start of the script, I init the object like so:
$scope.historicalDailyPercentages = []; //TODO, this should be initialised with data from the database.
I understand I'll need to have some sort of $http get loop in there, to check for the data and fill the object, but I'm a little unclear on how to get started there.
The entire goalzy.js script is below for reference. Thanks in advance!
angular.module('goalzy', [])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.headers.post['Content-Type'] = 'application/json; charset=utf-8';
}])
.controller('TodoController', ['$scope', '$http', function($scope, $http) {
$scope.todos = [];
$scope.historicalDailyPercentages = []; //TODO, this should be initialised with data from the database.
$scope.addTodo = function() {
if ($scope.todoText != "") {
if ($scope.todos.length < 3) {
$scope.todos.push({
text: $scope.todoText,
done: false
});
$scope.todoText = '';
//Save to DB
} else {
alert("You can only have 3 todos per day!");
$scope.todoText = '';
}
} else {
alert("you must write something");
}
};
$scope.remaining = function() {
var count = 0;
angular.forEach($scope.todos, function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.percentComplete = function() {
var countCompleted = 0;
angular.forEach($scope.todos, function(todo) {
countCompleted += todo.done ? 1 : 0; //Simply calculates how many tasks have been completed
console.log(countCompleted);
});
var totalCount = $scope.todos.length;
var percentComplete = countCompleted / totalCount * 100;
return percentComplete;
}
$scope.finaliseDay = function(percentComplete) {
alert("You're finalising this day with a percentage of: " + percentComplete);
var today = new Date();
var alreadyPresent = $scope.historicalDailyPercentages.some(function(item) {
return item.date.getFullYear() === today.getFullYear() &&
item.date.getMonth() === today.getMonth() &&
item.date.getDate() === today.getDate();
});
//Confirm that nothing has alreayd been posted for today
if (!alreadyPresent) {
// Simple POST request example (passing data)
$http.post('/postDailyPercentage.php', {
user_id: 1,
percent: percentComplete,
date: today
}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
if (data) {
$scope.historicalDailyPercentages.push({
user_id: 1,
percent: percentComplete,
date: today
});
} else {
alert("Something went wrong" + data);
}
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
console.log("Post failure");
});
} else {
alert("You're all set for today - see you tomorrow!");
}
//console.log($scope.historicalDailyPercentages);
}
}]);
To populate that object with an $http.get you can do it as follows:
function getHistoricalDataSuccess(data) {
$scope.historicalDailyPercentages = data;
}
function getHistoricalDataError(error) {
//handle the error
}
$http.get('path/to/api')
.success(getHistoricalDataSuccess)
.error(getHistoricalDataError);
var TodoController = function($scope, HistoricalDailyPercentageService) {
HistoricalDailyPercentageService.get().then(function(percentages) {
$scope.historicalDailyPercentages = percentages;
}, function(error) {
alert(error);
});
};
var HistoricalDailyPercentageService = function($http) {
this.get = function() {
return $http.get('yourUrl')
.then(function(xhr) {
var data = xhr.data;
// Transform the data as you see fit
return data;
}, function(xhr) {
// xhr contains the error message - modify this as you see fit.
return xhr.code;
});
};
};
angular.module('goalzy')
.controller('TodoController', ['$scope', 'HistoricalDailyPercentages', TodoController])
.service('HistoricalDailyPercentageService', ['$http', HistoricalDailyPercentageService]);
I would recommend doing it this way; this will make it easier to test by taking the logic of getting the data out of your already busy controller. #RVandersteen's example will only work inside of your controller, which is fine, but it really does make your controller very busy; controllers should really only assign things to a scope, everything else should be handled in a directive (for example, binding events to methods) or a service/factory/provider (for business logic).
After you have finished up your code, could you post on CodeReview? There's a few improvements I could suggest but they are merely review-based things and not appropriate for the scope of this question.
It's worth noting by the way that because I am using then in the controller I must use then in the service, too. If I use success in the service then my changes will not be reflected when I call then in the controller.
I'm trying to make a service that will load persons from the server on demand. The first version looked like this:
services.factory('PersonServiceOld', function(Restangular, ErrorService) {
var persons = [];
var requesting = [];
var get = function(id) {
if (requesting[id]) {
return persons[id];
}
requesting[id] = true;
persons[id] = {'id' : id, 'photoName' : '0.png'};
Restangular.one('persons', id).get().then(function(success) {
persons[id].firstName = success.firstName;
persons[id].lastName = success.lastName;
persons[id].photoName = success.photoName;
}, function(failure) {
requesting[id] = false;
ErrorService.serverError(failure);
});
return persons[id];
};
var reset = function() {
persons = [];
requesting = [];
};
return {
getPerson : get,
clearCache : reset,
};
});
That way I get a reference to an object right away and it will be filled with data slightly after. It worked well... until I noticed that in another use case, I also want to request the address of a person like
var person = PersonService.get(id);
person.one(address).get().then(.......
but the objects returned from my PersonService aren't Restangular objects. So I tried something else:
services.factory('PersonService', function(Restangular, ErrorService) {
var persons = [];
var get = function(id) {
if (!persons[id]) {
persons[id] = Restangular.one('persons', id);
persons[id].get().then(function(success) {
}, function(failure) {
ErrorService.serverError(failure);
persons[id] = null;
});
}
return persons[id]; // also tried: persons[id].$object
};
return {
getPerson : get
};
});
I hope somebody understands what I'm trying to do here and can give me a good pointer on how to achieve this.
Check this Plunkr for a complete example.
As Restangular returns promises, and your get function may be asynchronous or synchronous (in case you use your own cache), you need to create a promise for returning always the same type of object.
You can do it as described in the Angular documentation for $q service.
So your get function may look like :
var get = function (id) {
var deferred = $q.defer();
if (store[id]) {
deferred.resolve(store[id]);
} else {
Restangular.one('person', id).get().then(function (res) {
store[res.id] = res;
deferred.resolve(res);
}, function (err) {
deferred.reject(err);
});
}
return deferred.promise;
}
Then in your controller, for retrieving your data :
PersonService.get(475).then(function (person) {
// stuff
}, function (err) {
// err handling
});
I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});