How to use angularFireCollection initial load callback? - javascript

I've been trying to handle properly in my angular service an explicit synchronization with Firebase. I use angularFireCollection with initial load callback function (I need to preload additional data basing on the data returned by the first query). However, I don't know how should I access the fetched data in the callback function:
getGroupIds: function() {
var deferred = $q.defer();
var ref = new Firebase('https://<XXX>.firebaseio.com/groups');
angularFireCollection(ref, function(groups) {
console.log(groups);
deferred.resolve(groups);
});
return deferred.promise;
}
In the above example, how can I access actual data from groups object?d
Thanks in advance for any tips.

A Firebase snapshot is provided as an argument to the callback function, and you can extract the value from it as follows:
angularFireCollection(ref, function(snapshot) {
console.log(snapshot.name() + " has value " + snapshot.val());
});

usually, I'm setting the collection to scope, but either of these seem to work.
var groups = angularFireCollection(ref, function() {
console.log(groups);
});
or in a controller
$scope.groups = angularFireCollection(ref, function() {
console.log($scope.groups );
});

Related

promises only working properly with then function

I have a button which executes a function with a promise that gets and displays data from firebase in html (I'm using angularJS, ionic and firebase).
The problem is : if I don't inlclude a .then(function(){}) after it, the promise gets executed in unsynchronous way, meaning I have to click the button once again so the data gets displayed in html.
I want to put the data in the scope after the promise (that gets the data from firebase), but for some reason in only works if I put a .then function after it.
However, the data gets displayed normally in the console, but not in html (meaning I think that the function doesn't get attached to the scope).
Here is the piece of code :
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$scope.chatArray = snapshot.val();
deferred.resolve()
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise.then(function(){
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Any solutions? I don't have much experience with promises but I didn't find anything related. Cheers.
the purpose of promise is to manage asynchronous methods, so I don't really understand the problem...
Moreover normally the code inside displayChat must be executed, only the callback must be executed after. You should return the promise, that enable you to execute callback once you are sure the required asynchronous method are done.
When changes to scope are done by events external to the AngularJS framework, the framework needs to do an $apply to initiate a digest cycle to update the DOM.
(source: angularjs.org)
The .then method of a $q service promise automatically initiates the necessary digest cycle. In this case, the promise returned by the displayChat function is discarded and no digest cycle gets initiated. The subsequent click of the button initiates the digest cycle.
In the future, someone might wish to chain from the promise returned by the displayChat function. I recommend making the function more versatile by returning a proper promise and moving any changes to scope into a .then method.
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
//ref.on("value", function(snapshot) {
//USE once
ref.once("value", function(snapshot) {
console.log(snapshot.val());
//$scope.chatArray = snapshot.val();
deferred.resolve(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
//ADD rejection case
deferred.reject(errorObject);
})
return deferred.promise.then(function(chatArray){
//Move scope changes here
$scope.chatArray = chatArray;
//RETURN to chain data
return chatArray;
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Also to avoid memory leaks, use ref.once instead of ref.on and be sure to reject the promise in the error case.
Promises are used to defer execution of some logic until the promise has been satisfied - ie: you have received your results from the database. In your case, you are already deferring your console display and setting of the $scope variable within ref.on. The promise code is redundant.
The fact that your result shows in the console proves you have received the result. When you update data in the scope, it will not appear until a digest cycle has occurred. Most of the time, Angular can automatically figure out when it needs to run a digest cycle. On those times when it does not, you can force it by wrapping your scope related logic in a timeout, in which case, your code would look as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$timeout(function () {
$scope.chatArray = snapshot.val();
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
}
Your use of the promise .then method just happened to trigger a digest cycle. The promise itself really wasn't doing anything.
If your intention was to pass the snapshot back to the caller when it became available, that's when the promise comes into play, and would be done as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
deferred.resolve(snapshot)
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise;
};
$scope.callDisplayChat = function () {
$scope.displayChat().then(function (result) {
$scope.chatArray = result.val();
});
};

Handling multiple Ajax Requests inside another Ajax Request

I'm using angularjs' $http method to get multiple "parent" elements. In this Ajax Calls .success method, I have to iterate over the parent elements, and use yet another Ajax call for every parent element, to get its respective child elements. What I want in the end, is an Array containing all the child element objects, so I can display them using ng-repeat. That's why I want to collect all the child elements in an array first, and the write that array to the scope array I'm using to display, so angular will only update when all child elements are collected. I'm not that versed in using promises, but I think this should be possible by using them. The structure is basically:
.success(function(parentElements){
var tempChildElements = [];
$.each(parentElements, function(i, parentElement){
getChildElements(parentElement)
.success(function(childElements){
tempChildElements.concat(childElements);
})
})
});
$scope.childElements = tempChildElements;
});
Basically, I need to know when all the requests inside jQuery.each are finished.
EDIT:
So, I changed my code to incorporate your answers, and I think I'm close but it's still not working. What I got is:
$scope.loadChildren = function(){
var tempchildren = [];
var promises = [];
restApi.getOwnparents() //Returns $http.get promise
.then(function(parents){
parents.data.forEach(function(parent, i, parents){
promises.push(restApi.getOwnchildren(parent) //Returns $http.get promise
.then(function(children){
tempchildren = tempchildren.concat(children.data);
},function(msg){
console.log(msg);
}));
});
}, function(msg){
console.log(msg);
});
$q.all(promises).then(function(data){
//Never gets called
$scope.currentElements = tempchildren;
console.log(tempchildren);
});
};
EDIT 2:
I got it to work using suggestions from you guys, below is my code. Feel free to share improvements.
$scope.loadparents = function(){
var tempchildren = [];
var promises = [];
restApi.getOwnparents()
.then(function(parents){
parent.data.forEach(function(parent, i, parents){
promises.push(restApi.getOwnchildren(parent));
});
$q.all(promises).then(function(data){
console.log(data);
data.forEach(function(children){
tempchildren = tempchildren.concat(children.data);
});
$scope.currentElements = tempchildren;
});
});
};
Something like this might be a possibiliy. Loop through your parentElements calling getChildElements with that element. However the response from getChildElements will be a promise if your returning the $http call so push that into an array and pass that array to $q.all. When all your ajax calls resolve so will $q.all.
var parentElements = [10, 20, 30, 40],
promises = [];
parentElements.forEach(function(i){
//Each method is actually called here
promises.push(getChildElements(i));
});
//$q.all will resolve once all of your promises have been resolved.
$q.all(promises)
.then(function (data){
//handle success
console.log('All Good', data);
//Modify your response into what ever structure you need, map may be helpfull
$scope.childElements = data.map();
});
Most likely your ajax call won't be resolved by the time the array is passed to $q.all however another nice thing about promises is even if they are all resolved $q.all will resolve straight away instead.
See it in action. http://jsfiddle.net/ht9wphg8/
Each request itself returns a promise, which can then be put into an array and pass that array to $q.all().
The success() callback is deprecated and since you need to return promises you need to use then() anyway in your original request callback.
Here's a sample factory that would make all the requests and once done you would have the parent data of first request returned to controller:
app.factory('DataService', function($http, $q) {
return {
getData: function() {
// return initial promise
return $http.get('parent.json').then(function(resp) {
var parentArr = resp.data;
// create array of promises for child data
var promises = parentArr.map(function(parentItem, i) {
// return each child request promise to the array
return $http.get('child.json').then(function(resp) {
console.log('Child request #' + (i + 1) + ' completed');
// update parent item
parentItem.child = resp.data
});
});
// return the promise wrapping array of child promises
return $q.all(promises).then(function() {
console.log('All requests completed');
// when all done we want the parent array returned
return parentArr;
});
});
}
};
});
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData().then(function(parentArr) {
console.log('Add data to scope')
$scope.parentArr = parentArr;
});
});
DEMO

Angular - ngResource breaks data binding

I am new to Angular, and am trying to get up to speed with ngResource.
I created a factory in my chapter.service.js file
angular.module('myApp')
.factory('Chapter', function ($resource) {
return $resource('/api/book/chapter/:id'); // Note the full endpoint address
});
matchescontroller.js
angular.module('myApp').controller('matchesCtrl', function($scope, $location, Chapter) {
// This is used to get URL parameters
$scope.url = $location.path();
$scope.paths = $scope.url.split('/');
$scope.id = $scope.paths[2];
$scope.action = $scope.paths[3];
//Trying to call the test data
var chapters = Chapter.query();
$scope.myFunction = function() {
alert(chapters.length);
}
My view where I test the function
<button ng-click="myFunction()">Click Here</button>
I created a test function to test whether my query returned any results. When I click on the button, I'm alerted with 0, which means the query didn't work.
When I change the function to
$scope.myFunction = function() {
console.log(Object.keys(chapters));
}
I get [$promise, $resolve], but none of the Schema keys
I must be doing something wrong, but I was looking at this tutorial
http://www.masnun.com/2013/08/28/rest-access-in-angularjs-using-ngresource.html
Any help will be appreciated.
Edit: Here is the response I got from the server
GET http://localhost:9000/api/book/chapter/1 500 (Internal Server Error)
$scope.myFunction = function() {
Chapter.query({}, function(data) {
$scope.chapters = data;
}, function(error) {
// custom error code
});
}
When working with $resource I prefer to use the success/error handlers that the API comes with as opposed to dealing the promise directly. The important thing to realize is that just because you called query does not mean that the result is immediately available. Thus the use of a callback that handles success/error depending on what your backend returns. Only then can you bind and update the reuslt in the UI.
Also, while we're talking about it I notice that you didn't wire up the optional paramter in your $resouce URL. $resource takes a second paramter which is an object that supplies mapping for the /:id part of your route.
return $resource('/api/book/chapter/:id', {id: '#id'});
What this notation means is that you pass an object to $resource that has a property called id, it will be subbed into your URL.
So this:
$scope.item = {id: 42, someProp: "something"};
Chapter.get({$scope.item}....
Will result in an API call that looks like '/api/book/chapter/42'
You get a promise from $resource and not the "result" from your database. The result is inside the promise. So try this
var chapters = Chapter.query();
$scope.myFunction = function() {
chapters.then(function(data) {
console.log(data);
});
}
I must admit, that I am not thaaaaat familiar with ngResource, so Jessie Carters is right, the correct syntax is:
chapters.get({...}, function callback() {...})

asynchronous callback returns null

I am trying to assign callback function values and display ng-repeat
var myApp = angular.module('mybet',[]);
myApp.controller('MyBetController', function($scope,$firebase,ProfileFactory,MybetFactory){
$scope.myBetEvents = MybetFactory.getUsersBetsProfile(currentLoggedUser, function(betsData){
//Callback retuning null value here
$scope.myBetEvents = betsData;
});
});
myApp.factory('MybetFactory', ['$firebase', function($firebase){
var factory = {};
factory.getUsersBetsProfile = function(userId, callback){
var firebaseRef = new Firebase("https://xxx.firebaseio.com/usersbetsprofile").child(userId);
var firebaseBetsRef = new Firebase("https://xxx.firebaseio.com/events");
var userBettedEvent = [];
//retrive the data
firebaseRef.on('value', function (snapshot) {
console.log(snapshot.val());
var data = snapshot.val();
if (data){
//callback(data);
angular.forEach(data, function(data){
console.log('For Each getUsersBets',data);
var firebaseEventsData = firebaseBetsRef.child(data.event_id);
//retrive the data of bets
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
console.log('userBettedEvent',userBettedEvent);
//if I call callback here then i get the values but calling callback multipul time is no the option
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
});
//ISSUE:: callback returing null values
callback(userBettedEvent);
}else{
console.log('Users has no bets');
}
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
};
return factory;
}])
View::
<ul class="list">
<li class="item" ng-repeat="events in myBetEvents">{{events.event_name}}</li>
</ul>
How can get callback() to return values and display in view? what is causing callback after foreach returning null?
As #coder already said in the comments, your callback(userBettedEvent) is in the wrong place. Where you have it now, it will be invoked before the on('value' from Firebase has completed.
From the comments it is clear that you've notice that, yet only want the callback to invoked for one child. You can simply use a limit for that.
Something like this would work:
if (data){
data.limit(1).on('value', function(childData) {
var firebaseEventsData = firebaseBetsRef.child(childData.event_id);
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
callback(userBettedEvent);
}
})
I haven't really tested the code, but I'm quite sure a limit should get you in the right direction.
As a side note: you seem to be using Firebase as a traditional database, pulling the data out like you'd do with SQL. As you may notice: that is not a natural model for Firebase, which was made to synchronize data changes. If you want to stick to pulling the data, you may consider using once('value' instead of on('value' to ensure the handlers fire only once for every time you try to pull the data from Firebase.

angularjs - $http reading json and wait for callback

I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.

Categories

Resources