Angular $q.all() combine the previous operation with current operation - angularjs

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.

Related

AngularJS: Retrieve mysql data in electron and publish it to AngularJS scope

I'm trying to retrieve a list of data from mysql database by using electron and bind it to a list in the controllers scope. I'm using mysql2. Here is my controller:
$scope.carList = [];
mysql.execute("SELECT * FROM cars").spread(function(results){
$scope.carList = results;
console.log(results);
})
I do get the results back, but the in the view carList remains empty. How can I solve this problem?
I just added a button to my view and bound it to a check function like this:
$scope.check = function(){
console.log($scope.carList);
}
After I click on the button, my list in the views gets populated. Now my question would be how can I have my list populated on the start of the controller rather than wait for an event ro make it happen?
I think mysql.execute("").spread(fn) promise is not a part of the AngularJS digest cycle. You did not provide enough code to fully reproduce your problem but I think by triggering a new digest cycle it should work for you. E.g. try it with $timeout which triggers a new digest cycle.
$scope.carList = [];
mysql.execute("SELECT * FROM cars").spread(function(results){
$timeout(function () {
$scope.carList = results;
});
})
I would prefer to create a AngularJS service which handles your electron mysql in a nice way. You could globally apply your $scopes in it, right after finishing your mysql procedures which are not a part of your digest cycle.
Approach by using AngularJS promises
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, $q) {
$scope.carList = [];
getCars.then(function(cars) {
$scope.carList = cars;
});
function getCars() {
var deferred = $q.defer();
mysql.execute("SELECT * FROM cars").spread(function(results) {
deferred.resolve(results);
});
return deferred.promise;
}
});

How to Return Asynchronous ($http) Data on Modal Close or Cancel

I have a open modal in controller called "Audit"
$scope.comment = function (spec){
ManualService.setManual(spec);
var modalInstance = $modal.open({
templateUrl: 'views/comment.html',
controller: 'CommentCtrl',
size: 'lg'
});
}
When the users clicks on comment button the modal opens and user can add comments. On closing the comment modal i'm trying to update the modal in "Audit" controller but it's not happening
Below function is in a different controller called "Comments"
$scope.cancel = function () {
$http.post('/api/v1/manual/manual',$scope.id).success (function (data) {
$scope.manual = data;
ManualService.setManual($scope.manual);
}).error(function (data, status) {
console.log('Error ' + data);
});
$modalInstance.dismiss('cancel');
};
My question is how do I return the new data I got from calling the endpoint on cancel function without reloading the page
Return the promise returned by the $http service:
$scope.cancel = function () {
var promise = $http.post('/api/v1/manual/manual',$scope.id)
.then (function (response) {
var manual = response.data;
return manual;
});
$modalInstance.dismiss(promise);
};
Then chain from the result:
$modal.open({}).result.then(
function onClose(value) {
// Use the value you passed from the $modalInstance.close() call
},
function onCancel(manual) {
ManualService.setManual(manual);
$scope.manual = manual;
}
)
The $modal service creates a $scope that is destroyed when the modal closes. Use the promise returned by the modal service to update the proper $scope.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
— AngularJS $q Service API Reference - Chaining Promises
See also UI Bootstrap Modal DEMO and API Reference
The $modal.open() function returns a few things, and you're looking for the result property. Here's the documentation, take a look at the result property.
The result property returns a promise that you can chain on in order to do certain things when the modal is either "closed" or "dismissed".
When closing a modal, you have two options:
$modalInstance.close(value)
$modalInstance.dismiss(value)
You can use either one, but I'd suggest using the close() function for "successful" completion, and dismiss() for "cancelled" or "failed" modal operations. Typically the "x" button in the top right of the modal will call the dismiss() function, so you can handle dismissal separately from completion.
So, you'll end up with something like this:
$modal.open({}).result.then(
function (value) {
// Use the value you passed from the $modalInstance.close() call
},
function (dismissed) {
// Use the value you passed from the $modalInstance.dismiss() call
}
)

Wait for event to happen in server side in angular JS

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.

How can I fetch a list over and over?

I want to have a list in sync, so I'm (right now) polling it every seconds. But I seem to have a problem - it does not work:
app.controller("MainController", function ($scope, $http, $timeout) {
$scope.responsePromise = $http.get("http://localhost:52219/API/GetList");
$scope.responsePromise.success(function (data, status, headers, config) {
$scope.model.list = JSON.parse(data);
$timeout(function ()
{
console.log("reload");
$scope.responsePromise = $http.get("http://localhost:52219/API/GetList");
}, 1000);
});
My goal is retrieving a list every X sec from the server that talks to the database. Anyone know why does does not spam "reload"? I only get it once
You are looking for $interval, and not $timeout.
from $interval docs:
Angular's wrapper for window.setInterval. The fn function is executed every delay milliseconds.
and also:
Note: Intervals created by this service must be explicitly destroyed when you are finished with them. In particular they are not automatically destroyed when a controller's scope or a directive's element are destroyed. You should take this into consideration and make sure to always cancel the interval at the appropriate moment.
As said by #Nitsan Baleli, you should use the service "$interval" and not "$timeout".
The question was asked here, please see the answer : Angular JS $timeout vs $interval
My goal is retrieving a list every X sec from the server that talks to the database.
I rewrote your code so that it matches your goal, it becomes:
app.controller("MainController", function ($scope, $http, $timeout) {
var $scope.model = {
list:[]
};
var getData = function(){
$http.get("http://localhost:52219/API/GetList").success(function(data){
$scope.model.list = JSON.parse(data);
});
};
getData(); // for the first call
$interval(function (){
getData();
}, 1000);
});
See the plunkr demo here: http://plnkr.co/edit/xCbGGyKPTeJtg7TeKKyE?p=preview

How would I re-instantiate an AngularJS controller after a change in data?

I have looked, and assume this is simple, but just couldn't figure out the API documentation for this.
Assume I have a controller that pulls data when first called (I'm leaving out a ton, of course):
myCtrl = function ($scope, Data) {
$scope.data = [];
data_promise = Data.getData(); //a $http service
data_promise.success(function (data) {
$scope.data = data;
});
}
That works great, and when the page loads I get $scope.data populated exactly as I need it. However, of course, the user may wish to update the data. Assume a simple service "Data.save()" called when a server clicks a "save" button on a form:
myApp.factory('Data', function ($http) {
save: function (data) {
$http({
method: 'POST',
url: 'someURL',
data: data,
}).success(function(){
//something here that might trigger the controller to refresh
});
};
});
What would I put in the success callback that might re-instantiate the controller so that it has the most up-to-date data from the server? Currently I am having to refresh the page to get the updated data. I am not worried right now about minimizing server calls by cashing results and changes. I just need to get this to work first.
Thanks!
You do not need to refresh. Simply change the updated data in the ControllerScope.
This should work.
myApp.factory('Data', function ($http) {
save: function (data, $scope) {
$http({
method: 'POST',
url: 'someURL',
data: data,
}).success(function(){
$scope.data = newData;
//something here that might trigger the controller to refresh
});
};
});
// in your controller
Data.save(data, $scope);
But: You shouldn't do this way. This looks messy. Use services or events which you watch to wait for the changes comming back from the service.
OK, although I am sure there is a better answer I have one for me. Essentially I am taking the important parts of the controller and placing them in the success callback. In order to keep it from looking messy, I have wrapped all the parts of the controller that need be updated in a named function.
myCtrl = function ($scope, Data, $q) {
// Binding the Data
var updateAll;
updateAll = function () {
$scope.data1 = [];
$scope.data2 = [];
$scope.data3 = [];
$scope.data4 = [];
service_promise1 = Data.getData1(); //a $http service
service_promise2 = Data.getData2();
service_promise3 = Data.getData3();
service_promise4 = Data.getData4();
$q.all([service_promise1,service_promise2,service_promise3,service_promise4])
.then(function([service1,service2,service3,service]){
$scope.data1 = service1 + service2 //I know this isn't valid Javascript
// just for illustration purposes
$scope.data2 = service2 - service1 + service 3
//etc...
});
};
updateAll();
//Form Section
$("#myForm').dialog({
buttons: {
Save: function () {
Data.save(data).success(function(){
updateAll();
});
}
}
});
}
Breaking this down, I have wrapped all the assignments to scope objects that rely on services into the updateAll function and invoke it on instantiation of the myCtrl. In the form that updates the data I call the updateAll() function upon success of the Data.save() function.
Not exactly brain surgery, I'll admit, but I had gotten confused with $scope.$apply() and thinking about just calling myCtrl(). That somehow seemed like the "Angular" way, but neither worked. I guess the controller function gets run only once on page refresh and there is no Angular way to call it again.

Resources