$scope.$on("stopSpinner", function() {
$('body').mask('hide');
});
the above code works that when stopSPinner is event is recieved, it executes. But my requirement is little more involved. I want to wait for two different events to recive before executing a function. Order of events could be different but i want to function to only execute once both events have occured. how can this be achived
Use $q.all and promises, like this:
var deferred1 = $q.defer();
$scope.$on("stopSpinner", function()
{
$('body').mask('hide');
deferred1.resolve();
});
var deferred2 = $q.defer();
$scope.$on("event2", function()
{
$('body').mask('hide');
deferred2.resolve();
});
$q.all([deferred1, deferred2]).then(
function(){
//your code after the 2 events...
})
This way, you'll be able to wait for 2 async task to complete in a single function.
Related
I am really new to angularJS. I need to develop a page where angular JS wait for a event to happen at server side so angular JS should keep checking server using $http call in every 2 seconds. Once that event completes Angular should not invoke any $http call to server again.
I tried different method but it gives me error like "Watchers fired in the last 5 iterations: []"
Please let me know how to do it.
Following is my code
HTML
<div ng-controller="myController">
<div id="divOnTop" ng-show="!isEventDone()">
<div class="render"></div>
</div>
</div>
Angular JS
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http) {
$scope.ready = false;
$scope.isEventDone = function () {
$scope.ready = $scope.getData();
return $scope.ready;
};
$scope.getData = function () {
if (! $scope.ready) {
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
});
}
};
setInterval($scope.isPageReady, 5000);
});
A few things here.
I'm not convinced the accepted answer actually works nor solves the initial problem. So, I'll share my 2 cents here.
$scope.ready = $scope.getData(); will set $scope.ready to undefined each time since this method doesn't return anything. Thus, ng-show="!isEventDone()" will always show the DOM.
You should use angular's $interval instead of setInterval for short-polling in angular.
Also, I've refactored some redundancy.
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $interval) {
var intervalPromise = $interval($scope.getData, 5000);
$scope.getData = function () {
if (! $scope.isEventDone) {
$http
.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
if($scope.isEventDone) {
$interval.cancel(intervalPromise);
}
});
}
else {
$interval.cancel(intervalPromise);
}
};
});
This should work and solve your initial problem. However, there's a scenario where your server may be on a high load and takes 3 seconds to respond. In this case, you're calling the server every 2 seconds because you're waiting for 5 seconds after the previous request has started and not waiting for after the previous request has ended.
A better solution than this is to use a module like async which easily handles asynchronous methods. Combining with $timeout:
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $timeout) {
var getData = function(cb){
if(!$scope.isEventDone) return cb();
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
cb();
});
};
// do during will run getData at least once
async.doDuring(getData, function test(err, cb) {
// asynchronous test method to see if loop should still occur
// call callback 5 seconds after getData has responded
// instead of counting 5 seconds after getData initiated the request
$timeout(function(){
cb(null, !$scope.isEventDone);
// if second param is true, call `getData()` again otherwise, end the loop
}, 5000);
}, function(err) {
console.log(err);
// if you're here, either error has occurred or
// the loop has ended with `$scope.isEventDone = true`
});
});
This will call the timeout after the request has ended.
A better alternative, if you have control of the server, is to use a websocket which will enable long-polling (server notifies the client instead of client making frequent requests) and this will not increase significant load on the server as clients grow.
I hope this helps
In your example $scope.pageIsReady does not exist. What you could do is inject the $timeout service into your controller and wrap your http call inside of it:
var timeoutInstance = $timeout(function(){
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
if($scope.ready){
$timeout.cancel(timeoutInstance);
else
$scope.getData();
}
});
},5000);
cancel will stop the timeout from being called. I have not tested this but it should be along those lines.
Also not sure what type of backend you are using but if it is .net you could look into SignalR which uses sockets so the server side tells the front end when it is ready and therefore you no longer need to use polling.
Because code is too complex, i can only paste the structure of the code:
angular.module('xxx').controller('xxxCtrl', ['$scope', ..., '$q', function($scope, ..., $q){
...
//every checked unchecked event in multi-checkbox trigger the change in measurement
$scope.$watch('measurement', function (newValue, oldValue) {
pendingRequests = {
primary: $q.defer(),
...: $q.defer()
};
var promises = _.map(pendingRequests, function (deferred) {
return deferred.promise;
});
$q.all(promises).then(function (results) {
$scope.data = results[0];
$window.console.log($scope.data);
});
//Here i have a $http request to server
ResultService.requestResultsOnce();
});
}])
The scenario is like this, we have multiple checkboxes on the page, every change to the multi-checkboxes will trigger the watch method.
If i click the checkbox with normal speed, it works perfect.
But if i click 2 checkboxes with very fast speed, the callback in
$q.all(promises).then
will be called only once.
Does anyone can help on this?
Actually the every $http success callback is correctly executed 2 times, why the then method execute only 1 time?
It seems $q.all combine the last time request with the new click request.
I am new to AngularJS. I am testing resolve in order to display a partial view only after receiving a response from the server (is it the good way to do that?).
To undestand better how resolve works, I would like to add a pause (5 seconds) in the service which uses $resource to get the data displayed by the partial view. In Java I would use the method sleep. Is there a simple way to wait for 5 seconds with AngularJS before executing the following lines of code (before using $resource in my service)?
Something like this will wait:
function ExampleController($scope, $timeout, $q) {
function get() {
var request = $q.defer();
console.log('Waiting...');
$timeout(function() {
request.resolve({data:1});
}, 5000);
return request.promise;
}
get().then(function(response) {
console.log(response.data);
});
}
Basically you set up a $q.defer(), and then resolve it when everything that you want is loaded (or in this case when the timeout finishes). You then use the then function to do something with the data you resolved.
Create a factory like so:
function fakeResource($q, $timeout) {
return {
get: function() {
var request = $q.defer();
$timeout(function() {
request.resolve({data:1});
}, 5000);
return request.promise;
}
}
}]);
app.factory('fakeResource', fakeResource);
Then in the resolve of your $routeProvider:
customers: function(fakeResource) {
return fakeResource.get();
}
Promises are one of those things that I can understand while I'm looking at them, but then the comprehension vanishes when I look away.
I need to grab two pieces of data asynchronously, then combine the result and store it in a cookie. I think I could implement a vanilla promise chain without much difficulty. That is,
loadData1().then(loadData2).then(setCookie);
However, I don't need to wait for one request to finish before making the other. How can I do something like
(loadData1(); loadData2();).then(setCookie);
Here's a quick example using $q.all:
$q.all([
(function () {
var d = $q.defer();
API.get({}, function (data) {
d.resolve(data);
});
return d.promise;
})(),
(function () {
var d = $q.defer();
API.getMore({}, function (data) {
d.resolve(data);
});
return d.promise;
})()
]).then(function(responses) {
//responses contains an array of all your data responses, in the order they were chained to $q
});
Click run a couple of times - these tests alternate between pass and fail.
http://jsfiddle.net/samselikoff/hhk6u/3/
Both tests require companies, but I don't know how to isolate the events. Any ideas?
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
You are running synchronous tests while the callbacks of the triggered events are asynchronous.
To fix that you have to implement an "asyncTest" and call the start function when the test assertions are ready to be collected.
Your second test was failing with the message:
Called start() while already started (QUnit.config.semaphore was 0
already)
teste
Exactly because it was a synchronous test, already started and you were calling the start() method again.
And also in your first test, that doesn't specify a callback function, you have to wrap your async call in another function so you can call start() when the simulated AJAX call is ready.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
Note that in the first test method I created a "getCompanies" function that will be called after an interval (500 milliseconds) that should be enough for the AJAX call to finish.
You have to adjust this time according to your needs, and also ajust "testTimeOut" value so your methods won't run indefinitely.
See QUnit config docs for more details: http://api.qunitjs.com/QUnit.config/
Isn't your initial Fiddle potentially failing because you are not creating your event bus at the start of each test (in a setup() method), so your asynchronous event from the first test could be fired when the 2nd test is running and then cause the 2nd test to handle it twice, calling start() twice.
See my updated Fiddle http://jsfiddle.net/e67Zh/ it creates the event bus each time.
You might want to also set a timeout in your qunit tests for scenarios where the event doesn't fire.
/* Backbone code
*******************/
var Company = Backbone.Model.extend({});
var Companies = Backbone.Collection.extend({
initialize: function() {
var self = this;
events.on("userSet:company", function(company) {
self.selectedCompany = company;
// Simulate an AJAX request
setTimeout(function() {
events.trigger("fetched:departments", [1, 2, 3]);
}, 500);
});
},
selectedCompany: ''
});
/* Tests
*******************/
module("test with new event bus each time", {
setup: function() {
events = _.clone(Backbone.Events);
}
});
test('Some test that needs companies.', function() {
var companies = new Companies();
ok(1);
});
test('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});