I'm building Angular/Express app, I load data with controller and try to work with data in a function but I get error in console
Cannot read property 'toLowerCase' of undefined
When I manually write JSON data it works just fine.
Anyone had this error and why is it happening?
Edit: Also I want function to work on click, when I want it not when it's loaded, also I use data from listData in view so I know it's loaded
Controller
var self = this;
self.listData = [];
var self = this;
self.listData = [];
$http.get('/myList')
.success(function (data) {
self.listData = data;
console.log(data);
})
.error(function (data) {
console.log('Error: ' + data);
});
self.myFunc = function(){
var map = self.listData.reduce(function (p, c) {
p.set(c.name.toLowerCase(), c.surname);
return p;
}, new Map());
console.log(...map);
}
HTTP.GET is an asynchronous function
You could call your function which turns the data to lowercase in the .success of your http.get. That way you know that the data has arrived. Now you might be executing this function a bit too early which means that you do not yet have the data in your list.
If you try to run the toLowerCase() on your data, before you actually retrieved the data you will get this error. That is one of the things you learn to deal with when working with web requests.
For example writing your code like this would work.
$http.get('/myList')
.success(function (data) {
self.listData = data;
myFunc(listData);
console.log(data);
})
.error(function (data) {
console.log('Error: ' + data);
});
}
function myFunc(){
var map = self.listData.reduce(function (p, c) {
p.set(c.name.toLowerCase(), c.surname);
return p;
}, new Map());
console.log(...map);
}
Here is your updated code works on click of an element:
jQuery("#a-div-to-click").on("click", function() {
var self = this;
self.listData = [];
$http.get('/myList').success(function (data) {
self.listData = data;
console.log(data);
self.myFunc();
}).error(function (data) {
console.log('Error: ' + data);
});
}
self.myFunc = function(){
var map = self.listData.reduce(function (p, c) {
p.set(c.name.toLowerCase(), c.surname);
return p;
}, new Map());
console.log(map);
}
});
V2) The data is loaded at "onload" phase and the process done at "onclick" phase:
app.controller('yourController', function ($scope, $http) {
$scope.fetchData = funcion(onSuccess) {
$http.get('/myList').success(function (data) {
$scope.aDivlistData = data;
console.log(data);
if (onSuccess != null) {
onSuccess();
}
}).error(function (data) {
console.log('Error: ' + data);
});
}
}();
$scope.onADivClicked = function() {
if ($scope.aDivlistData == null) {
$scope.fetchData($scope.populateMap);
} else {
$scope.populateMap();
}
};
$scope.populateMap = function() {
var map = $scope.aDivlistData.reduce(function (p, c) {
p.set(c.name.toLowerCase(), c.surname);
return p;
}, new Map());
console.log(map);
}
}
//html part:
//<div id="a-div-to-click" ng-click="onADivClicked()">A Div</a>
Just by looking at your code. It looks like "c.name" is undefined. May be you can print that variable out and see what's in it
c.name is undefined for some item in your listData. Checkout JSON which you receive from server, not faked one.
NOTE: $http.get is asynchronous.
Putting self.myFunc = ... into success handler of $http.get suppose to give correct behaviour. You can take a look on Understanding Asynchronous Code in Layman's terms to see how async works.
Good Luck ! :)
Related
I have been searching for an answer to this, and cannot seem to find anything. I have a service, in the first block I am successfully logging a url that I then need to pass into my getData() function. But it comes back undefined, I have tried the method below, and I tried moving the first $http.get into the controller where I am calling it, as well as moving the first $http.get into the getData() function. Am I going about this all wrong?
di.service('testService', function($http) {
$http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function(response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
return urlToJsonFile;
});
this.getData = function(urlToJsonFile) {
console.log(urlToJsonFile);
return $http.get('http://localhost:1337/' + urlToJsonFile).
then(function(response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
}});
$http is an async request. so you need to chain it inside the first request to ensure the value of first response is available when second request is called.
di.service('testService', function($http) {
var getData = function () {
return $http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function(response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
var urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
$http.get('http://localhost:1337/' + urlToJsonFile).
then(function(response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
});
}
return { getData: getData; }
});
I would suggest you to use a factory instead of a service
Check out the below code
di.factory('testService', function ($http) {
var variable_name;
var serviceMethodName = function () {
$http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function (response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
variable_name = urlToJsonFile; //added
});
}
//modified parameter in below method
var getData = function (variable_name) {
var urlToJsonFile = variable_name; //added
console.log(urlToJsonFile);
return $http.get('http://localhost:1337/' + urlToJsonFile).
then(function (response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
}
//Exposes the two methods and accessbile through out the app unless it is modified
return {
serviceMethodName: serviceMethodName,
getData:getData
}
});
I have a function which does a fetch, it returns successful and sets the data.
But I can't work out how to get the data out of the model again.
fetchAcceptedTerms: function () {
var self = this;
this.appAcceptedTerms = new T1AppAcceptedTerms();
this.acceptedTerms = new AppAcceptedTerms();
this.acceptedTerms.fetch({
success: function (data) {
console.log(data);
if (data.meta.status === 'success') {
self.appAcceptedTerms.set(data.data);
}
}
});
console.log(self.appAcceptedTerms);
console.log(self.appAcceptedTerms.attributes);
},
See output in console:
http://s32.postimg.org/ssi3w7wed/Screen_Shot_2016_05_20_at_14_17_21.png
As you can see:
console.log(data); returns the data as expected
console.log(self.appAcceptedTerms); the data is set correctly as we can see it in the log
console.log(self.appAcceptedTerms.attributes); isn't working properly and returns Object {}
Can someone help on how to get all of the attributes out?
Thanks
The fetch operation is asynchronous, so you need to check for your attributes after the fetch operation has completed. Does the below output your attributes as expected?
fetchAcceptedTerms: function () {
var self = this;
this.appAcceptedTerms = new T1AppAcceptedTerms();
this.acceptedTerms = new AppAcceptedTerms();
this.acceptedTerms.fetch({
success: function (data) {
console.log(data);
if (data.meta.status === 'success') {
self.appAcceptedTerms.set(data.data);
console.log(self.appAcceptedTerms);
console.log(self.appAcceptedTerms.attributes);
}
}
});
}
angular.module('App').factory('API', ['someAPI', function(someAPI){
var service = {};
service.loadInfo= loadInfo;
return service;
function loadInfo(id) {
var list = [];
var items= [];
someAPI.list.get({id: id}).$promise.then(function (result) {
items= result;
if (items.length === 0) {
items= 'No results';
} else {
for (var i=0; i<items.length; i++) {
list.push(items[i].name);
}
}
console.log('List:' + list); **//This print out 'List: a, b, c, d' in console**
})
console.log('List:' + list); **//This print out nothing**
return list;
}
}
]);
//Controller
angular.module('App').controller('ProductCtrl', function($scope, API) {
$scope.loadInfo = function (id) {
$scope.list = API.loadInfo(id);
console.log($scope.list); // This print out undefined
}
}
This might be a silly question, In my service, the variable inside 'someAPI' function could be visited, but controller get an undefined. Appreciate if any body could explain me. Thanks!
#James In your controller you need to pass $scope to your function (this is called dependency injection) which in simple words means passing an object to a function. Hope it helps!
the list gets returned immediately before the someAPI.list.get({id: id}).$promise is resolved. You need to do it in the promise way. Another problem is that your local variable is called list but you returned List.
I'd suggest that in your service, just do
return someAPI.list.get({id: id}).$promise.then(function (result) {
items= result;
if (items.length === 0) {
items= 'No results';
} else {
for (var i=0; i<items.length; i++) {
list.push(items[i].name);
}
}
return list;
})
}
and put move .then part to your controller.
$scope.loadInfo = function (id) {
someAPI.loadInfo(id).then(function (result) {
$scope.list = result;
});
}
Modified the dependencies and rewrite the factory call.
//Controller
angular.module('App').controller('ProductCtrl', function(API,$scope) {
$scope.loadInfo = function (id) {
API.loadInfo(id)
.success(function (data, status, headers, config) {
$scope.list = data;
console.log($scope.list);
}).error(function (e, status, headers, config) {
//log error here
});
}
Try this it will update you when ajax finish its process.
$scope.$watch('list',function(val){
console.log(val);
});
I'm new to AngularJS and am still trying to wrap my head around using services to pull data into my application.
I am looking for a way to cache the result of a $http.get() which will be a JSON array. In this case, it is a static list of events:
[{ id: 1, name: "First Event"}, { id: 2, name: "Second Event"},...]
I have a service that I am trying to use to cache these results:
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) {
if (ignoreCache || !eventListCache) {
eventListCache = $http.get("/events.json", {cache: true});
}
return eventListCache;
}
});
Now from what I can understand I am returning a "promise" from the $http.get function, which in my controller I add in a success callback:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
eventListService.get().success(function (data) { $scope.events = data; });
}
]);
This is working fine for me. What I'd like to do is add an event to the eventListService to pull out a specific event object from eventListCache.
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) { ... }
//added
this.getEvent = function (id) {
//TODO: add some sort of call to this.get() in order to make sure the
//eventListCache is there... stumped
}
});
I do not know if this is the best way to approach caching or if this is a stupid thing to do, but I am trying to get a single object from an array that may or may not be cached. OR maybe I'm supposed to call the original event and pull the object out of the resulting array in the controller.
You're on the right track. Services in Angularjs are singeltons, so using it to cache your $http request is fine. If you want to expose several functions in your service I would do something like this. I used the $q promise/deferred service implementation in Angularjs to handle the asynchronus http request.
appServices.service("eventListService", function($http, $q) {
var eventListCache;
var get = function (callback) {
$http({method: "GET", url: "/events.json"}).
success(function(data, status) {
eventListCache = data;
return callback(eventListCache);
}).
}
}
return {
getEventList : function(callback) {
if(eventListCache.length > 0) {
return callback(eventListCache);
} else {
var deferred = $q.defer();
get(function(data) {
deferred.resolve(data);
}
deferred.promise.then(function(res) {
return callback(res);
});
}
},
getSpecificEvent: function(id, callback) {
// Same as in getEventList(), but with a filter or sorting of the array
// ...
// return callback(....);
}
}
});
Now, in your controller, all you have to do is this;
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
// First time your controller runs, it will send http-request, second time it
// will use the cached variable
eventListService.getEventList(function(eventlist) {
$scope.myEventList = eventlist;
});
eventListService.getSpecificEvent($scope.someEventID, function(event) {
// This one is cached, and fetched from local variable in service
$scope.mySpecificEvent = event;
});
}
]);
You are on the right track. Here's a little help:
appServices.service("eventListService", function($http, $q) {
var eventListCache = [];
function getList(forceReload) {
var defObj = $q.defer(), listHolder;
if (eventListCache.length || forceReload) {
listHolder= $http.get("/events.json", {cache: true});
listHolder.then(function(data){
eventListCache = data;
defObj.resolve(eventListCache);
});
} else {
defObj.resolve(eventListCache);
}
return defObj.promise;
}
function getDetails(eventId){
var defObj = $q.defer();
if(eventId === undefined){
throw new Error('Event Id is Required.');
}
if(eventListCache.length === 0){
defObj.reject('No Events Loaded.');
} else {
defObj.resolve(eventListCache[eventId]);
}
return defObj.promise;
}
return {
eventList:getList,
eventDetails:getDetails
};
});
Then, in your controller, you handle it like this:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
var eventList = eventListService.getList();
eventList.then(function(data){
$scope.events = data;
});
$scope.getEventsList = function(reloadList){
eventList = eventListService.getList(reloadList);
eventList.then(function(data){
$scope.events = data;
});
};
$scope.getEventDetails = function(eventID){
var detailsPromise = eventListService.getDetails(eventID);
detailsPromise.then(function(data){
$scope.eventDetails = data;
}, function(reason){
window.alert(reason);
});
}
}
]);
This way, your events are loaded when the controller first loads, and then you have the option to request a new list by simply passing in a boolean. Getting event details is also handled by an internal promise to give you some error handling without throwing a disruptive error.
I am trying to store my friends list (name, picture and gender) on a var, but it doesn't work correctly. Please ready the comments in the code for the details. Any ideas? Thanks.
function getFriendsArray() {
var friendsArray = [];
FB.api('/me/friends?fields=name,picture,gender', function(response) {
if(response.data) {
var data = '';
$.each(response.data, function(indice, item) {
alert(item.name); // Friend's name are displayed correctly
friendsArray.push(item); // I believe this doesn't work
});
}
else {
errorHandler('getFriendsArray', JSON.stringify(response));
}
});
alert(friendsArray.length); // Returns 0 (incorrect)
return friendsArray.sort(sortByName);
}
The call function(response) is asynchronous. You should insert the alert after $.each
for completeness: you should change your approach to problem: do not call a function that returns an Array but that call a second function.
function getFriendsArray(callback, errorHandler) {
var friendsArray = [];
FB.api('/me/friends?fields=name,picture,gender', function(response) {
if(response.data) {
var data = '';
$.each(response.data, function(indice, item) {
alert(item.name); // Friend's name are displayed correctly
friendsArray.push(item); // I believe this doesn't work
});
callback(friendsArray);
}
else {
errorHandler('getFriendsArray', JSON.stringify(response));
}
});
}
getFriendsArray(function(friends) {
alert(friends);
},
function(error) {
alert('error');
});
It looks like the FB.api makes an asynchronous request (like jQuery.ajax) and so you are executing alert(friendsArray.length) before the FB.api call completes and pushes the results into the friends array.