AngularJS Long Polling - bug with number changing to 0, then switching - angularjs

I am simulating polling a server by manually changing db values, and allowing my controller to query the api every 10 seconds. Things work, however if the DB changes, then the value in the view switches to 0 before changing to the number it should be.
Controller
messageControllers.controller('NavigationCtrl', ['$scope', '$routeParams', 'Message', '$timeout',
function ($scope, $routeParams, Message, $timeout) {
var messages = Message.query({ type:'inbox' });
$scope.inbox = messages;
var poll = function() {
$timeout(function() {
var messages = Message.query({ type:'inbox' });
$scope.inbox = messages;
poll();
}, 10000);
};
poll();
}]);
Snippet from View
<li><a data-ng-href="#/">Inbox <span class="badge"><% (inbox|filter:{read:false}).length %></span></a></li>
e.g. if the number of read==false results is 5, then the db changes and now the number of read==false results is 6. What happens is instead of <% (inbox|filter:{read:false}).length %> changing straight to 6, it changes to 0 first.
Just getting to grips with Angular, and don't really understand what I am doing so sorry if this is a dumb question!!!
I have a feeling that it has something to do with the bound value in (inbox|filter:{read:false}).length being empty whilst AngularJS is waiting for the data, however I have no idea how to change this so that $scope.inbox only changes once Message.query is complete.

Message.query({ type:'inbox' }); is an async request.
It will return a reference object, which is populated after the query is finished. From the docs:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
Handily, you can access the promise which is resolved when the query finishes using the $promise property.
So try changing your code inside the timeout function to the following:
var messages = Message.query({ type:'inbox' });
// Populate the scope only after the query has resolved.
messages.$promise.then(function(){
$scope.inbox = messages;
});
poll();

Related

Utilising $scope.apply()

I have the following which works fine, drawing info from a RESTful api feed
app.controller('servicesController', ['$scope', '$location', '$http', '$interval',
function($scope, $location, $http, $interval) {
var getData = function() {
// Initialize $scope using the value of the model attribute, e.g.,
$scope.url = "https://(remote link to JSON api)";
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
});
};
getData();
$interval(getData(), 10000);
}
]);
However my view is not updating every 10 seconds as expected. I have read that I need to use $scope.apply() somewhere in this above code.
I tried placing the following (in the appropriate place above)
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
$scope.apply(); //I also tried $scope.runningServices.apply()
});
$scope.apply is not your problem, the scope will be digested automatically at the end of the $http request and $interval. Certain actions automatically "inform" Angular that the scope may have changed and trigger a digest; only if you're writing "non-Angular" code may you have to explicitly trigger a scope digest, since otherwise Angular wouldn't notice any changes.
No, your issue is that you're calling getData(), and then have its return value (undefined) execute every ten seconds. Which is obviously nonsense. You just want to pass the function itself to $interval:
$interval(getData, 10000);
// look ma, ^^^^^, no parentheses

I am unable to access $rootScope in my controller

I have some parameters in the $rootScope as specified below:
myApp.factory('itemService', function($http) {
return $http.get('/items');
});
myApp.run(function($rootScope, itemService) {
itemService.success(function(response) {
$rootScope.items = response;
});
});
myApp.controller('displayCtrl', function($rootScope, $scope) {
$scope.items = $rootScope.items;
});
When I run the above code, I get this error from firebug
TypeError: $rootScope.items is undefined. I really do not know what is happening.
Here is a small addition. items is an array with a list of objects like this:
items = [
{'name': 'spoon', 'price': 200},
{'name': 'table', 'price': 400},
{'name': 'shoe', 'price': 250}
];
I wish to make items available constantly in my app such that I can display each item on the item list (items) without making another request to the server. I intend to achieve this by simply displaying an item using $scope.item = items[$routeParams.id] each time I need to display an item.
I look forward to implement this using either a function attached to ng-click or the normal #/route/:param mechanism.
Thanks
TypeError: $object.property is undefined is usually because a request to a reference of an object is made before that specific object (or its property) has been set. $http requests are asynchroneous by nature so other processes do not get blocked. It should be obvious that trying to make requests synchroneous could cause a major issue for people with very slow connections.
Apart from that, polluting the $rootScope is generally a bad idea. You can find a topic about global variables on the following link so that you investigate why the $rootScope is not such a good place.
Having said all that, it seems to me that you didn't want to make multiple requests to retrieve the same data. If so, you can use the cache option for $http.get methods.
e.g:
myApp.factory('itemService', function($http, $q) {
return {
get: function() {
return $http({
url: 'items.json',
cache: true //keep the result in memory
});
}
};
})
myApp.controller('aCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
myApp.controller('bCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
This will make sure the information gets requested once and put into a cache. The data is accessible in different places.
<div ng-controller="aCtrl as a">
{{a.items}}
</div>
<div ng-controller="bCtrl as b">
{{b.items}}
</div>
This leaves me with another 'good' practice: the usage of the controllerAs syntax. Which provides a way to use namespaces in AngularJS.
Ofcourse, these are just tips and you should always consider the requirements!
You run asynchronious method at run block :
itemService.success(function(response){
$rootScope.items = response;
});
But initialization goes on, so probably you access $rootScope.items before itemService succeed (or it fails, and you didnt predict such situation). I suggest you to do this (if you want to follow $rootScope convension.. which is bad by the way) :
$rootScope.items = [];
itemService.success(function(response){
$rootScope.items = response;
});
You are setting items in the callback of an asynchronous process, so you are trying to access items on the $rootScope before its actually set.
If you are trying to initialize items when the controller is loaded, then there are other ways to do that such as using the resolve block of a route or manually calling the $http.get on the factory when the controller loads.
Finally, I was able to come up with a solution. I realized that the problem was to have $rootScope.items available in displayCtrl at the same time it loads. But $rootScope.items is available in my view when my html page loads.
So I simply passed the item id as a parameter and obtained it using $routeParams as follows
myApp.controller('displayCtrl', function($routeParams, $scope) {
$scope.item_id = $routeParams.id; //given that the route looks like '/item/:id'
});
Then in my HTML file this what I did
<div ng-bind="items[item_id].name"></div>
<div ng-bind="items[item_id].price"></div>
This actual solved my problem.

Why can't get the value stored in a variable of the controller

here is my controller:
(function(){
'use strict';
angular
.module('app')
.controller('HomeController', ['Factory',
HomeController]);
function HomeController(Factory){
var result = Factory.query();
this.model = result[0];
}
})();
in the view I get the value by doing so
div(ng-controller='HomeController as HomeCtrl')
h3{{HomeCtrl.model}}
the problem is I don't get any value. I am totally sure the value is returned from the factory since I have already tested the Factory.query() and I got the results. In fact If I do in the following way and get the value in the view by doing HomeCtrl.result[0], I get the result.
(function(){
'use strict';
angular
.module('app')
.controller('HomeController', ['Factory',
HomeController]);
function HomeController(Factory){
this.result = Factory.query();
}
})();
Well, the factory's query method is most certainly an asynchronous method which immediately returns an empty array, but only populates it once the results of the asynchronous query are available (when it gets the response from an AJAX request, for example):
var query = function() {
var result = [];
$http.get('/somePath').success(function(data) {
copyDataElementsTo(data, result); // 1 second later, the result is populated
}
return result; // returned immediately, empty
};
So, since your controller doesn't wait for the query result, it assigns result[0], which is undefined at this moment, to its model variable.
Your second way of doing works because, in that case, the view re-renders itself as soon as the response to the asynchronous request has been received, so it reevaluates result[0], which contains the elements received from the AJAX request.
You should use $scope to pass data to the view. That way, Angular will bind data and automatically update the view when your scope variable changes (your model variable is probably not available at the time the view renders due to an async call). Try something like this:
Controller
$scope.model = result[0];
View
h3{{model}}
Since you want to get the first element of the array once it is populated, you can do so in the callback:
Factory.query(function(result) {
this.model = result[0];
});

Getting variable from socket.io in AngularJS

Basically i have two sources of data, one is real time data from socket.io and other is json object. And i'm using both in front-end but the problem is that i need to pass a variable from socket.io to json parser:
This controller for my view:
.controller('mainCtrl', ['$scope','socket','currentData', function($scope, socket, currentData){
// It's updated every 2 seconds
socket.on('chnl', function(data){
// Passed to view OK
$scope.realtimeData = data;
// And i need to pass this to currentData.
$scope.foo = data.foo;
});
// Here i'm getting json response from factory which is computed based on socket.io foo variable and then passed to view.
currentData.get().then(function(data){
if($scope.foo)...
...
$scope..
});
}]
The problem is anything i tried i ended up calling json object on every incoming socket.io package, what i need to calc this at it's initalization and pass data to the view.
Any solutions?
Thanks.
If you need for it to run only once for initialization...
Move the call to the JSON service into the .on callback. Place it inside of a conditional which runs only when an initialization variable is set to false. Once the data is set, switch that variable to true so that it doesn't run again:
.controller('mainCtrl', ['$scope','socket','currentData', function($scope, socket, currentData){
$scope.fooInit = false;
socket.on('chnl', function(data){
$scope.realtimeData = data;
$scope.foo = data.foo;
if (!$scope.fooInit) {
currentData.get().then(function(data){
if($scope.foo)...
...
$scope..
});
$scope.fooInit = true;
}
});
}])

angularfireCollection: know when the data is fully loaded

I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}

Resources