I am using AngularJs in my application. I make a http call and getting the response. The response contains an array which I am putting as resultinside the $scope object. I am placing a watch on some attribute and try to access the stored object put inside the $scope object. If I print the result object, I see that it contains the array, but when I try to use the array properties such as length it throws an error. I am also not able to use other array methods such as results.data[0].
Please let me know where I am going wrong. Some code for understanding purpose:
var processResponse = function (result) {
$scope.results = result.data;
}
$scope.$watch('attribute', function(newVal) {
console.log($scope.results.length)
});
Depend how you call this. If the result is a promisse you can try do it:
result.then(successCallback, errorCallback);
Just try to declare/init $scope.results = []; at the top of your controller.
Then the method length couldn't crash ;)
Since it was asynchronous request, I had to take care of null by the following code:
if ($scope.results && $scope.results.data){
console.log($scope.results.data.length);
}
Related
In my global controller (assigned to the body in html) I have the following code to grab a snapshot of firebase one time.
ref.once('value', function(snapshot){
var data = snapshot;
}).then(function(data){
$rootScope.content = data.val();
});
I am using the content in my HTML templates to render them. All the questions I found had resulted in putting the "then" code inside the callback.
Literally all I need to do is be able to grab a snapshot one time and store it on $rootScope.content and access it outside of the function, i.e. anywhere in my app.
I'm pretty sure it's because everything is rendering prior to firebase returning anything, I'm just not sure the best way to tell it to wait.
Thank you in advance.
Try:
ref.once('value', function(snapshot){
$rootScope.content = snapshot.val();
});
Or:
ref.once('value').then(function(snapshot){
$rootScope.content = snapshot.val();
});
From the once() docs:
Return Value
Returns a Promise that can optionally be used instead of the successCallback and failureCallback to handle success and failure.
I think your second line var data = snapshot; may be the problem? If all you want is to get the whole database and assign it to a object called $rootScope.content, or anything else, the below would work. I don't use angular but $rootScope.content would need to be an object, aka var foo = {} would be able to take snapshot.val(). Note that snapshot.val() is full of stuff other than just your JSON formatted data and you will have to extract out your individual nodes by calling the property names (the key's), e.g. snap.val().firstName to get Bob.
firebase.database().ref('/').once('value').then(function(snap) {
$rootScope.content = snap.val();
});
I am trying to get songs from soundcloud, I am using some input to set value and send it to my factory to get all the related list of songs and display it.
The issue is the the first time all works correctly, but when I am trying to input new values I am getting same results as first time.
My code looks like:
.controller('DashCtrl', function ($scope, SongsService) {
$scope.formData = {};
$scope.searchSong = function () {
SongsService.setData($scope.formData.songName);
};
UPDATE
the factory :
.factory('SongsService', function ($rootScope) {
var List = {};
List.setData = function (tracks) {
var page_size = 6;
SC.get('/tracks', {limit: page_size, linked_partitioning: 1}, function (tracks) {
// page through results, 100 at a time
List = tracks;
$rootScope.$broadcast('event:ItemsReceived');
});
};
List.getItems = function () {
return List;
};
return List;
}).value('version', '0.1');
Thanks for help!
It's hard to tell without a plunkr reproducing the issue and showing all your relevant code, but I think your problem is that you're overwriting the List variable in the async answer, and this List (I assume) is the object you originally returned from your factory.
I see two noteworthy concepts here:
the fact that angular factories are effectively singletons
and that javascript objects are passed by reference-by-value (see call-by-sharing, or one of many stackoverflow discussions).
An angular factory is a singleton, meaning the factory function will only be called once, before the first injection, and every controller it's injected into will work with the same object reference it returned. If you overwrite this object reference, well, the previous value (which the controller has) is still a reference to the original object.
Edit: In fact, by overwriting List you're creating a new object which doesn't even have a setData method anymore!
You probably want to make List private to SongsService, and return a more complex object from the factory that captures List in a closure, and offers some public getter/setter methods for it. (If you insist on replacing the contents of the returned List variable, empty the object and extend it with the new properties - including this method again. But this is much more work, and not a nice solution.)
In Angular Service constructors and Factory methods are singleton objects. You need to return a method that you can call. Your code examples are incomplete so it is hard to tell what is going on. What is returned by your factory method, the List object?
If so, when the first call is completed, it overwrites the List object so that the setData method can't be called a second time. What is the SC object, I can not see in your example how you are injecting it. You probably want to fix that too.
Consider this possible solution.
Service
Songs.$inject = ['$http'];
function Songs($http) {
this.$http = $http;
}
Songs.prototype.getSongs = function(searchTerm) {
return this.$http.get('http://someendpoint/songs', {searchTerm: searchTerm});
}
service('songs', Songs);
Controller
DashController.$inect = ['songs'];
functionDashController(songs) {
this.songs = songs;
this.results = [];
}
DashController.prototype.searchSongs = function(searchTerm) {
var self = this;
this.songs.getSongs(searchTerm).then(function(results) {
this.results = results;
});
}
controller('DashController', DashController);
This is example uses the best practice controllerAs syntax explained here: http://toddmotto.com/digging-into-angulars-controller-as-syntax/
I found the issue,
I got same results all the time because I didnt use cooreclty the api of soundcloud, I didnt send the title on the api... also you are correct, I should not set the list as empty..I should set some value to the list...
I am developing an AngularJS application and found the following behavior.
I have two functions in my service. The first function returns all the categories stored in the database and the second returns one category by its id.
Here is my service:
angular.module('categoriesRepository', [])
.service('categoriesRepository', ['$cordovaSQLite', 'sqliteHelper',
function ($cordovaSQLite, sqliteHelper) {
//this works - returns an array with all categories
this.getAll = function () {
var categories = [];
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories;")
.then(function (res) {
for (var i = 0; i < res.rows.length; i++) {
categories.push(res.rows[i]);
}
});
return categories;
}
//this works not - returns undefined
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
category = res.rows[0];
});
return category;
}
}]);
I know that I can use Angulars $q to run functions asynchronously, and use their values when they are done processing.
Why does the getById function return the category directly and the getAll wait until the array is filled?
EDIT
I had the getAll function posted wrong. There is no return statement before $cordovaSQLite.execute
UPDATE:-
After your question is updated.
In the first example your are creating an array first by doing var categories = [];and then returning this array before finishing your async call. When your async call completes it just pushes certain elements into the array thus not destroying the reference to the array (categories ) variable. When it is returned back if you will debug it you will find the function returning an empty array and later when the async call succeeds only then the array will be filled.
In the second example you are creating just a variable and then returning it before the async call finishes. But then the async call is finished you assign the variable to a new value. thus destroying the earlier reference.
Solution:-
Though not a preffered approach to make it work. you will have to maintain the category variable reference. for this you can use angular.copy OR angular extend
So the second part of your code should be like
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
angular.copy(res.rows[0], category);
//now the reference to the category variable
//will not be lost
});
return category;
}
Better Practice:-
The way you have been developing this application is wrong. Async calls should not be handled this way. I earlier asked a question just to clarify the way to handle the async calls and state inside the angular app, factories and controllers please have a look here. It provides two ways to handle the state and async calls. There might be many more practices out there but these two suit me best.
It is unfortunate that this approach appears to 'work' because it is caused by the modification of the returned array object "at some unspecified time" after it is returned.
In the usage the array is accessed/observed after1 it has been modified by the asynchronous call. This makes it appear to function correctly only because of the (accidental) asynchronous-later-than observation.
If the observation was prior to the actual completion of the SQLite operation - such as immediately after the getAll function call - it would reveal an empty array.
Both functions are incorrectly written and the first accidently releases Zalgo (or perhaps his sibling).
See How do I return the response from an asynchronous call? for more details.
1 Chrome's console.log can be confusing as it works like console.dir and thus may be showing the current value and not the value when it was invoked.
As stated already, this is a bad approach. You can't use result of your function immediately after it returns.
However I didn't see the answer to your exact question: why do they behave differently?
It happens because with an array you return a reference to an object (type Array). Later on you use same reference to modify contents of the object, i.e. push new items into the array.
However in second function you modify the reference itself. You make you local variable categories point to a new object. Thus old object (reference to which was returned to outer scope) remains untouched. To make it work the same way you should have written
category.row = res.rows[0];
You return the result of the execute in the first case, whereas you return the variable in the second case, which has most likely not been populated yet.
I'm hoping someone can shed some light on the scope of variables when using the $http AngularJS service.
My code looks like this:
app.controller('TestPlanRequestCtrl', function($scope, $http) {
$scope.tableData = []; // Populate the table with this array
$scope.tpRequests = null;
$http.get('common/data/requests.json').success(function(data) {
$scope.tpRequests = data.TPRequests;
});
Next I want to run a loop to put my data into an array like so:
for (var i = 0; i < $scope.tpRequests.length; ++i) {
var requestObj= {
requestNum: $scope.tpRequests[i].RequestNumber;
}
$scope.tableData.push(requestObj);
}
This works great if the for loop is inside the function called from the success method, but I think it would be cleaner to keep it outside the call. If I have the loop outside the call, I get the error:
Error: $scope.tpRequests is null
I don't understand why tpRequests is populated in the get call, and then the data is gone after the get call ends. I'm guessing it is considering the $scope.tpRequests inside the function call to be a different one than the one I declared above the $http.get(). What's the correct way to do this?
You can use a $watch on $scope.tpRequests to do your data manipulation, however I would recommend just doing it in the success promise if there are no other ways to trigger the watch and simply check for null/undefined before operating.
A great primer on use cases for $watch and $watchCollection:
http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm
I'm using Jquery's $.getJSON to get remote JSON from a server. The data is returned and stored in a global variable. The reason this variable is global is because I want that variable and it's data inside, to be the value of another variable outside of that function. So far, the variable with the global variable containing the JSON data, as it's value, has been "undefined" due to the rest of the code not waiting on the JSON. So I think a deferred object will help with this issue however I'm new to this idea. Here is my code below. How can I use a deferred object to allow my code to wait for the JSON data then define my variable outside of the function with the global variable containing the JSON data.
function getStuff(num, onContents){
$.getJSON('http://whateverorigin.org/get?url=' + encodeURIComponent('http://catholic.com/api-radio/' + num) + '&callback=?', function(data){
//call the oncontents callback instead of returning
onContents(data.contents);
});
}
//when calling getstuff, pass a function that tells what to do with the contents
getStuff(6387, function(contents){
window.GlobalVar = contents;
});
var NewVar = window.GlobalVar;
alert(NewVar);
It is obvious when reviewing our back-and-forth that there was a lot of confusion in our correspondence. Allow me to sum up:
First, you wanted to define a global variable with the result of your $.getJSON call. Your code was already doing this.
getStuff(6387, function(contents){
// the following statement will assign the value of 'contents' to the global variable 'GlobalVar'
window.GlobalVar = contents;
});
I was focussed on the last two lines of your snippet because they were breaking your code:
var NewVar = window.GlobalVar;
alert(NewVar);
The reason this doesn't work is because window.GlobalVar has not been defined yet. Although the line of code where window.GlobalVar is assigned the value of contents precedes the final two lines in the code, the evaluation of these lines is out of sync because the $.getJSON call is asynchronous (which basically means it has its own timeline). So, window.GlobalVar = contents; has not been executed when var NewVar = window.GlobalVar; is attempted - so the line fails.
Due to the asynchronism, any code that relies on contents has to be executed after the $.getJSON call - which is what you hinted at in your original question when you mentioned "deferreds". This is why I urged you to move your last two lines into your callback. Using a deferred would not have been essentially different from what you were already achieving with your callback - a deferred just allows you to attach multiple callbacks that fire on different events.
We finally established that your issue is specific to Angular JS - that is, how to attach ajax data to your $scope. Whereas using a jQuery deferred would not help you, I think using an Angular deferred will. What you need to do is to replace jQuery's getJSON method with the jsonp method from Angular's http service - this will allow you to attach a 'success' handler which will update your $scope (and your view) when your ajax data arrives:
function getStuff(num) {
$http.jsonp('http://whateverorigin.org/get?url=' + encodeURIComponent('http://catholic.com/api-radio/' + num) + '&callback=JSON_CALLBACK')
.success(function (contents) {
$scope.shows = contents;
});
};
getStuff(6387);
Our ajax response contains an object with a key of 'contents' and a value which is a string of json data, within which is the 'shows' data we want. I'm going to modify the .success handler so that we can get the 'shows' data by deserializing the json string:
.success(function (response) {
var result = angular.fromJson(response.contents);
$scope.shows = result.shows;
});
Next, in our Angular, we want to iterate over each 'show' in 'shows' and we can do this using the ng-repeat directive:
<ul>
<li ng-repeat="show in shows">{{ show.title }}</li>
</ul>