Im a building a system that fetches new events based on the date sent to the API using AngularJS. I am currently still in in the learning stages of AngularJS so Im wondering is there something Im missing.
My problem is , even though the data loads correctly on the page load, when I click the button the first time I am getting undefined for $scope.newEvents even though it is successfully getting the data from the API (I can see in the console).
I was attempting to build a JSFiddle but I was having trouble getting the whole thing to work on it.
here is the html output...
The output...
<ul>
<li ng-repeat="event in events | orderBy:'-event_date' ">
<span >{{event.event_date}},{{event.event_name}}, {{event.event_venue}}, {{event.event_description}} </span>
</li>
</ul>
The get request in my js...
var eventApp = angular.module('eventApp', ['ngRoute']);
eventApp.factory("services", ['$http', function($http) {
var apiBase = 'http://example.net/angular-api/'
var service = {};
service.getEvents = function(latest_date){
return $http.get(apiBase + 'events?latest_date=' + latest_date);
}
return service;
}]);
When the page loads its working fine, it makes a successful request and updates $scope.events ...
var date = new Date();
$scope.todays_date = date.getUTCFullYear() + '-' +
('00' + (date.getUTCMonth()+1)).slice(-2) + '-' +
('00' + date.getUTCDate()).slice(-2) + ' ' +
('00' + date.getUTCHours()).slice(-2) + ':' +
('00' + date.getUTCMinutes()).slice(-2) + ':' +
('00' + date.getUTCSeconds()).slice(-2);
$scope.events = [{}];
services.getEvents($scope.todays_date).then(function(data){
$scope.events = data.data;
});
But when click the button that loads the ng-click function I am getting undefined for my data. I initially figured there was something wrong with the date format, but the fact that it is successfully getting the data is what is confusing me. The problems start at then below...
$scope.addFiveNew = function (new_or_old) {
if (new_or_old == 'new') {
$scope.latest_date = $scope.getNewestDate();
services.getEvents($scope.latest_date).then(function(data){
$scope.newEvents = data.data;
});
console.log($scope.newEvents);
// update the list (this is currently failing on first click)
angular.forEach($scope.newEvents,function(item) {
$scope.events.push(item);
});
}
this function in the controller creates the date to send...
$scope.getNewestDate = function() {
var sortedArray = $filter('orderBy')($scope.events, 'event_date');
var NewestDate = sortedArray[sortedArray.length - 1].event_date;
return NewestDate;
};
Here is an example of some data...
{
"event_id":"1",
"event_name":"Event 1",
"event_date":"2014-09-26 00:00:00",
"event_venue":"Limerick",
"event_description":"stuff"
}
The bit inside the then is asynchronous, so the rest of your code can run without the scope being updated by the return of your service method, right?
services.getEvents($scope.latest_date)
.then(function(data){ //whenever the service returns
$scope.newEvents = data.data; //update the scope
console.log($scope.newEvents); //log the results
// update the list
angular.forEach($scope.newEvents,function(item) {
$scope.events.push(item);
});
});
It looks to me like the getEvents call on your service is returning a promise. Calling .then on a promise will call the passed function asynchronously. So, your $scope.newEvents = data.data; will get called after the rest of the statements in that block. I think you can change your .then statement to the following and it should work:
$scope.addFiveNew = function (new_or_old) {
if (new_or_old == 'new') {
$scope.latest_date = $scope.getNewestDate();
services.getEvents($scope.latest_date).then(function(data){
$scope.newEvents = data.data;
console.log($scope.newEvents);
// update the list (this is currently failing on first click)
angular.forEach($scope.newEvents,function(item) {
$scope.events.push(item);
});
});
}
}
Related
I've been working on the problem for about a week now with no progress. I have an array of data that's available to my $scope. I iterate through it like this:
<div ng-repeat="device in myData">
<label>{{processor(device.$id)}}</label>
</div>
The data contains only a Firebase $uid. And I want to make a second request to the database to get information thats associated with this $uid and place it as the label's content. I thought I could use an angular expression with a function to pass in the Firebase $uid and return some data.
I declare this $scope function:
$scope.processor = function(uid) {
function getDeviceInfo(callback) {
_.child('device/' + uid).once('value', function(snapshot) {
callback(snapshot.val())
})
}
getDeviceInfo(function(data) {
console.log(data)
return data
})
}
Basically I call the processor($id) in my scope, passing in the uid I want to lookup. The function getDeviceInfo() runs and has a callback, when the data is returned, I log it to the console, which works perfect, all the data is there. But then when I try and return a value to the $scope, it doesn't update.
I've tried about every combination of Angular/AngularFire code available and haven't gotten anything to work, any ideas?
If the function that you pass as a parameter to the once function is executed asynchronous, you can't return the data from the processor function.
The best you can do is to add the resulting data to the device object or to create another object to hold all the data for all the devices.
Try this:
<div ng-repeat="device in myData">
<label>{{device.data}}</label>
</div>
$scope.processor = function(device) {
_.child('device/' + device.$id).once('value', function(snapshot) {
device.data = snapshot.val();
});
}
}
$scope.myData.forEach($scope.processor);
Or this:
<div ng-repeat="device in myData">
<label>{{deviceData[device.$id]}}</label>
</div>
$scope.deviceData = {};
$scope.processor = function(device) {
_.child('device/' + device.$id).once('value', function(snapshot) {
$scope.deviceData[device.$id] = snapshot.val();
});
}
}
$scope.myData.forEach($scope.processor);
If that function is not asynch you can return the data using something like this:
$scope.processor = function(uid) {
var data = undefined;
_.child('device/' + uid).once('value', function(snapshot) {
data = snapshot.val()
})
return data
}
reference
Your function is not returning anything. Try this:
$scope.processor = function(uid) {
function getDeviceInfo(callback) {
_.child('device/' + uid).once('value', function(snapshot) {
callback(snapshot.val())
})
}
return getDeviceInfo(function(data) {
console.log(data)
return data
})
}
This seems overcomplicated though, why not do this?
$scope.processor = function(uid) {
return _.child('device/' + uid).once('value', function(snapshot) {
return callback(snapshot.val());
})
}
Check whether you have multiple instances of the same controller in your application. I made the same mistake a couple of times. I had ngRoute instantiating the controller using
app.config(function($routeProvider) {
$routeProvider
.when('/', {
// template : 'NIIIIICE',
templateUrl : 'pages/enquiries.html',
controller : 'firstcontroller'
});
});
Then I mistakenly created another instance of the SAME controller inside the HTML like:
ng-controller="firstcontroller as fctrl"
When I call $apply() it would only apply to child instance and that made it seem as if it wasn't working....So yeah long story short make sure you are running $apply in the same instance. :)
I can verify a record exists after adding it to a store but when I'm in another function and declare a new variable to point to the same database and store and execute a query I get the following error message.
Store: null not exists.
The method I'm using to retrieve the data was copied to the location where I'm adding the record just to make sure it's syntax was right and it works there. But when I'm in this other function it no longer finds the data.
Here is some code:
My goal is to keep track of when I cached data so that I can refetch data when it goes stale. The "updateCacheAge" method seems to work. The record object is populated with the JSON object I would expect.
updateCacheAge = function (dbName, cacheName) {
// updates the mashCacheAge. This helps track how old/stale a cache is.
var cacheJSON = {
id: cacheName,
updatedDate: new Date().getTime()
};
mashDB = new ydn.db.Storage(dbName);
mashDB.put({ name: 'mashCacheAge', keyPath: 'id' }, cacheJSON).done(function (key) {
mashDB.executeSql('SELECT * FROM mashCacheAge WHERE id = \'' + cacheName + '\'').then(function (record) {
$log.log('mashCacheAge record for: ' + cacheName);
$log.log(record);
});
});
$log.log(cacheName + ': mashCache was re-created.');
}
This is the isStale function. The first time through I would expect this to potentially fail because nothing is in cache. When that occurs I know to fetch my data then update the cacheAge to prevent me from going back to the database until the cache is stale.
I'm sure my code can be improved but this was my first pass at solving the problem. The problem I'm having is the data I just saved to the mashCacheAge store is not there and the error message seems to indicate the store itself is not there.
Am I doing something silly here?
isStale = function (dbName, cacheName, minutes) {
// db: is 'mashCache'
// store: No store is provided but might be added later to remove constaint 'mashCacheAge'
// subject: is the name of the cache being evaluated.
// minutes: is the number of minutes before the cache is considered stale.
var deferred = $q.defer();
var result = true;
// get milliseconds version of minutes.
var ageToleranceMilliseconds = (minutes * 60) * 1000;
var currentDateMilliseconds = new Date().getTime();
isStaleMashDB = new ydn.db.Storage(dbName);
try {
isStaleMashDB.executeSql('SELECT * FROM mashCacheAge WHERE id = \'' + cacheName + '\'').then(function (record) {
$log.log('mashCacheAge record for: ' + cacheName);
$log.log(record);
var durationMilliseconds = currentDateMilliseconds - record[0].updatedDate;
// Check if the data is stale.
if (durationMilliseconds > ageToleranceMilliseconds) {
result = true;
}
else { result = false; }
deferred.resolve(result);
});
}
catch (e) { deferred.resolve(true); }
return deferred.promise;
};
I located the enemy and the enemy is me.
My initial experimentation with this library worked and I left remnants of that experiment in the root of my module. I know better but it slipped my mind and cost me a couple hours of work. My bad.
I was reusing a "db" name for my YDN database and these conflicted. JavaScript didn't complain to me though. Once I commented out all my initial experimentation everything started working as expected.
first off : I'm new to node, and a relative programming beginner.
I'm trying to create a small web app with Express, whose only goal is to fetch and reformat data from a website that doesn't have an open API.
To do so, I've decided to learn about scraping, and that brought me to Cheerio and Request.
I'm using reddit as an example, to learn on. The end goal in this example is to gather the name and href of the posts on the front page as well as the url leading to the comments, then to go on that page to scrape the number of comments.
What follows is the route that is called on a GET request to / (please excuse the variable names, and the comments/console.logs, I got frustrated) :
/*
* GET home page.
*/
exports.index = function(req, res){
var request = require('request')
, cheerio =require('cheerio')
, mainArr = []
, test = "test"
, uI
, commentURL;
function first() {
request("http://www.reddit.com", function(err, resp, body) {
if (!err && resp.statusCode == 200) {
var $ = cheerio.load(body);
$('.thing', '#siteTable').each(function(){
var url = $('a.title', this).attr('href')
, title = $('a.title', this).html()
, commentsLink = $('a.comments', this).attr('href')
, arr = [];
arr.push(title);
arr.push(url);
arr.push(commentsLink);
mainArr.push(arr);
});
second();
};
});
}
function second() {
for (i = mainArr.length - 1; i >= 0; i--) {
uI = mainArr[i].length - 1;
commentURL = mainArr[i][uI];
console.log(commentURL + ", " + uI + ", " + i);
var foo = commentURL;
request(foo, function(err, resp, body) {
console.log("what the shit");
// var $ = cheerio.load(body);
// console.log(mainArr.length + ", " + commentURL + ", " + i + ", " + uI);
// var test = $('span.title', 'div.content').html();
console.log(test + ", "+ foo + ", " + commentURL + ", " + i + ", " + uI);
// mainArr[1][2] = test;
});
};
if (i<=0) {
res.render('index', {title: test});
};
}
first();
};
The function first(); works as intended. It puts the title, the href and url to the comments in an array, then pushes that array in a master array containing those data points for all of the posts on the front page. It then calls the function second();
Said function's goal is to loop through the master array (mainArr[]), then select all of the urls leading to comments (mainArr[i][uI]) and launch a request() with that url as first parameter.
The loop works, but during the second call of request() inside the second() function, everything breaks down. The variable i gets set permanently at -1, and commentURL (the variable that is set to the URL of the comments of the current post), is defined permanently as the first url in arrMain[]. There are also weird behaviors with arrMain.length. Depending on where I place it, it tells me that arrMain is undefined.
I have a feeling that I'm missing something obvious (probably to do with asynchronicity), but for the life of me, I can't find it.
I would be really greatful for any suggestions!
You are correct about your guess, it's the infamous "Javascript loop Gotcha". See here, for example, for an explanation:
Javascript infamous Loop issue?
Besides that, it seems that only your debug prints are affected. The commented code regarding var test ought to work.
Finally, the kind of language is frowned upon in SO, you would do well to take 2 minutes and change your variable names in this post.
I want to log objects using log4javascript. For example consider the following code:
function LogObject() {
var blah = {
one: 42,
two: "486"
};
logger.Info(blah);
Assuming that logger is instance of log4javascript logger that is properly set up:
var logger = log4javascript.getLogger("InternalLogger");
var ajaxAppender = new log4javascript.AjaxAppender(url),
jsonLayout = new log4javascript.JsonLayout(false, false);
ajaxAppender.setLayout(jsonLayout);
ajaxAppender.addHeader("Content-Type", "application/json");
logger.addAppender(ajaxAppender);
I am expecting the result to the following: request payload contains array of messages first of which is my object serialized into JSON. What I see is array of messages first of which has string "Object object" (like toString() method was invoked). How can I achieve that?
JsonLayout formats the logging event (which includes log level, timestamp and logger name in addition to the log message(s)) as JSON rather than the log message, which is pretty much assumed to be a string. The reason for this is to avoid a dependency on a JSON library for older browsers; generating JSON for the simple, known data that JsonLayout deals with is no problem without a JSON library but handling arbitrary objects definitely requires one.
The workaround I'd suggest is simply to format the message before you pass it to the logging call:
logger.info( JSON.stringify(blah) );
We were following #Tim Down's suggestion
logger.info( JSON.stringify(blah) );
But we had performance issues since the JSON.stringify happens before logger.info is called, therefore it will always happen even if the logging level is set to ignore this log.
In order to work around this I wrote a new lazy layout so that the stringification only happens if the log is actually output. In order to be more flexible it also alows passing a function, in which case it outputs the result of running said function.
Usage:
logger.trace("Received ", widget, " which has ", () => countFrimbles(widget), ' frimbles');
Implementation:
function LazyFormatLayout() { }
LazyFormatLayout.prototype = new log4javascript.Layout();
LazyFormatLayout.prototype.format = function (loggingEvent) {
var time = loggingEvent.timeStamp.toTimeString().split(/\s/)[0];
var head = time + ' ' + loggingEvent.logger.name + ' [' + loggingEvent.level.name + '] - ';
var body = loggingEvent.messages.map(function (arg) {
try {
switch (typeof (arg)) {
case 'function':
return arg();
case 'object':
return JSON.stringify(arg);
}
}
catch (e) {
return '<<error while logging: ' + e.stack + '>>';
}
return arg;
}).join('');
if (!loggingEvent.exception)
return head + body;
return head + body + ' ==> Exception: ' + loggingEvent.exception.stack;
}
LazyFormatLayout.prototype.ignoresThrowable = function () { return false; };
LazyFormatLayout.prototype.toString = function () { return "LazyFormatLayout"; };
Question is somewhat dated, but a simple google search turned up this question and there seems to be a build-in way to log objects:
var log = log4javascript.getDefaultLogger();
log.info("log following object",{ data:5, text:"bla" });
output
12:49:43 INFO - log following object {
data: 5,
text: bla
}
Thanks to #asgoth, I am able to use AngularJS $http service to retrieve stock prices from Yahoo as described here: Cannot read response from AngularJS $resource JSONP get from Yahoo Finance
In the "getHistoricalPrice" function, it puts the price inside an array, which is inside an object. From inside that function, I am able to access the price and write it to console.
The function returns the object to where it is called from. From there, I can successfully write the entire object out to console. However, I cannot access the elements of this object. I tried many different ways, but still cannot access the data in the object. You can see the code at http://jsfiddle.net/curt00/LTazR/2/ or below:
angular.module('app', ['ngResource']);
function AppCtrl($scope, $http, $resource) {
var historical_price = getHistoricalPrice("AAPL", 'start date is hard coded', 'end date is hard coded');
console.log("after calling historical price: ", historical_price); // historical_price is an object and all of the correct data is outputted to console here, but I cannot access its elements directly from Javascript.
for(var key in historical_price) {
console.log("key =",key); // this outputs "key = list"
}
console.log("after calling getHistoricalPrice: ", historical_price.list[0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
console.log("after calling getHistoricalPrice: ", historical_price['list'][0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
console.log("after calling getHistoricalPrice: ", historical_price[0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
function getHistoricalPrice(symbol, start, end) {
var query = 'select * from csv where url=\'http://ichart.yahoo.com/table.csv?s=' + symbol + '&a=' + '11' + '&b=' + '19' + '&c=' + '2012' + '&d=' + '11' + '&e=' + '19' + '&f=' + '2012' + '&g=d&ignore=.csv\'';
var url = 'http://query.yahooapis.com/v1/public/yql?q=' + fixedEncodeURIComponent(query) + '&format=json&callback=JSON_CALLBACK';
var histData = {};
$http.jsonp(url, {timeout: 30000}).success(function(json) {
var list = [];
var result = json.query.results.row;
result.shift(); // remove the header (columns) row
angular.forEach(result, function(row) {
list.push([(new Date(row.col0)).getTime()/1000, parseFloat(row.col4)]);
});
list.sort(function(val1, val2) {
return val1[0] - val2[0];
});
histData.list = list;
console.log('Loaded historical data',histData.list[0][1],', for ' + symbol); // This works and gives the price
});
return histData;
}
var fixedEncodeURIComponent = function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
};
}
Any help or suggestions to solve this problem is greatly appreciate!
It's a matter of timing.
In lines 12-14 you are trying to access histData.list before it has been populated. This is because this code is run before the success callback to the $http.jsonp function is executed.
Any code that depends on that callback being completed must be in the callback or in a function called in the callback.
See my answer on https://stackoverflow.com/a/13967709/1916258
A great way to debug the Yahoo api is using the YQL Console: http://developer.yahoo.com/yql/console/
Info about the different posibilities (which stock info) can be found on http://www.gummy-stuff.org/Yahoo-data.htm
Edit: there was still a problem with function fixedEncodeURIComponent. It should encode quotes (") too:
var fixedEncodeURIComponent = function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A").replace(/\"/g, "%22");
};
BobS is right, you aren't timing things correctly. Also you declared fixedEncodeURIComponent after you had called it. This was resulting in an immediate error when I loaded up the jsfiddle.
While you were passing the callback through to your function correctly, you weren't actually calling it. I stripped out all the post processing of the json as you have some other errors involving the query and just implemented the callback so you can see it working.
After the request is finished and you're still in the success function you need to add
if(typeof(callback) === "function"){
callback();
}
This calls that function you passed in and runs it. Here is a working jsFiddle of it:
http://jsfiddle.net/LTazR/22/
I also updated a new variable i created call output so you can see it changing.
Thanks to everybody for providing suggestions.
I solved the problem by using AngularJS' $scope variable, such as $scope.symbol[user].price. I created this variable before calling the getHistoricalPrice function and then in that function, after the result is returned from $http.jsonp, I put the value into the $scope variable, as such:
$scope.symbol[user].price = row.col4;