Imagine you have a view that is populated with an http call to some service that returns a list of objects to be shown. I'd like to refresh periodically this data. I found $interval
to acplish that behaviour.
The problem is that I'm not able to show a simple "isLoading" screen everytime I want to refresh the view.
I thought about something like:
viewModel.isLoading = true;
viewModel.refreshView();
viewModel.isLoading = false;
Of course, isLoading
is bind to some view message with angular. This isn't working, the property is always false. I found that it is because angular notifies the view of the property changed when the function called by $interval
exits, but when it exits isLoading
is always false.
How can I achieve to make isLoading
reflects its changes even if it is inside a function?
Plunker snippet (Angular 1.6.0):
My view:
<head>
<script src=".6.0/angular.js"></script>
<script src="testController.js"></script>
</head>
<body>
<div ng-app="testApp">
<div ng-controller="testController as viewModel">
<h1>
"IsLoading" value: {{ viewModel.isLoading }}
</h1>
</div>
</div>
</body>
My controller:
(function() {
angular.module('testApp', [])
.controller("testController", testController);
function testController($interval) {
viewModel = this;
viewModel.isLoading = true;
viewModel.refreshView = function () {
viewModel.DoSomeLongRunningOperation();
};
viewModel.DoSomeLongRunningOperation = function() {
//
}
var invertPropertyTimer = $interval(function () {
viewModel.isLoading = true;
viewModel.refreshView();
viewModel.isLoading = false;
}.bind(this), 1000);
}
})();
Imagine you have a view that is populated with an http call to some service that returns a list of objects to be shown. I'd like to refresh periodically this data. I found $interval
to acplish that behaviour.
The problem is that I'm not able to show a simple "isLoading" screen everytime I want to refresh the view.
I thought about something like:
viewModel.isLoading = true;
viewModel.refreshView();
viewModel.isLoading = false;
Of course, isLoading
is bind to some view message with angular. This isn't working, the property is always false. I found that it is because angular notifies the view of the property changed when the function called by $interval
exits, but when it exits isLoading
is always false.
How can I achieve to make isLoading
reflects its changes even if it is inside a function?
Plunker snippet (Angular 1.6.0): http://plnkr.co/edit/kIANiq6F0eM98KgHdr6i?p=preview
My view:
<head>
<script src="https://ajax.googleapis./ajax/libs/angularjs/1.6.0/angular.js"></script>
<script src="testController.js"></script>
</head>
<body>
<div ng-app="testApp">
<div ng-controller="testController as viewModel">
<h1>
"IsLoading" value: {{ viewModel.isLoading }}
</h1>
</div>
</div>
</body>
My controller:
(function() {
angular.module('testApp', [])
.controller("testController", testController);
function testController($interval) {
viewModel = this;
viewModel.isLoading = true;
viewModel.refreshView = function () {
viewModel.DoSomeLongRunningOperation();
};
viewModel.DoSomeLongRunningOperation = function() {
//
}
var invertPropertyTimer = $interval(function () {
viewModel.isLoading = true;
viewModel.refreshView();
viewModel.isLoading = false;
}.bind(this), 1000);
}
})();
Share
Improve this question
asked Jan 8, 2017 at 21:52
Francesco BonizziFrancesco Bonizzi
5,3026 gold badges54 silver badges89 bronze badges
7
-
1
call
viewModel.isLoading = false;
when loading actually finished. e.g. inside some promisethen
handler – Bryan Chen Commented Jan 8, 2017 at 22:09 - @BryanChen Can you please provide an example? – Francesco Bonizzi Commented Jan 9, 2017 at 8:03
-
refreshView
takes no time to run, so the loading time is none. – Bryan Chen Commented Jan 9, 2017 at 8:25 - This example is very simple, in my real case I have a real long running operation inside DoSomeLongRunningOperation() and it doesn't work either. – Francesco Bonizzi Commented Jan 9, 2017 at 8:31
-
1
In order to acplish this, either
refreshView
orDoSomeLongRunningOperation
would need to be a promise. – Claies Commented Jan 10, 2017 at 22:54
5 Answers
Reset to default 3 +50I'm not sure about this one, but what about using promises?
You can return a promise object in your refreshView function. This will help you to see if the task is done in your DoSomeLongRunningOperation
function later on in the interval.
viewModel.refreshView = function () {
return new Promise(function(resolve, reject){
viewModel.DoSomeLongRunningOperation();
resolve();
});
};
Do some tasks in your operation function. Lets say there is an operation that takes 200ms. (I used setTimeout
but angular has its own testable $timeout service also.)
viewModel.DoSomeLongRunningOperation = function() {
//do your thing.
setTimeout(function(){
console.log("timeoutends.")
}, 200);
}
In your interfal, listen the promise with then()
and change isLoading variable. That promise resolver will fire after DoSomeLongRunningOperation
func is done;
var invertPropertyTimer = $interval(function () {
viewModel.isLoading = true;
viewModel.refreshView().then(() =>{
viewModel.isLoading = false;
});
}.bind(this), 1000);
I haven't check the if it works or the syntax is right. Also you might need to use $apply()
if the data is changing but the view is not.
Links:
- https://davidwalsh.name/promises
- http://jimhoskins./2012/12/17/angularjs-and-apply.html
DoSomeLongRunningOperation
make http call? so it should return a Promise
and in the then
you should change the isLoading
to false
after the http
call respond.
other thing that i think you didn't thought about- what if the http call will take more time than the next interval time (then you will have two http calls at the same time)? i think you should call the refreshView
with $timeout
after the previous refreshView
done.
Here is a working example.
Use a Promise, and set isLoading = false
in the promise's then()
method! See it working HERE.
(function() {
angular.module('testApp', [])
.controller("testController", testController);
function testController($interval, $timeout) {
viewModel = this;
viewModel.isLoading = false;
viewModel.someData = "Haven't loaded anything yet...";
viewModel.refreshView = function() {
viewModel.isLoading = true;
viewModel.DoSomeLongRunningOperation();
};
viewModel.DoSomeLongRunningOperation = function() {
//Create a Promise object.
//This will likely be an $http.get() call.
var longRunningOperation = $timeout(function() {
return 'Some data retrieved at ' + new Date();
}, 500);
//This runs once the Promise has resolved
longRunningOperation.then(function(data) {
//Do something with the retrieved data
viewModel.someData = data;
//Now we're done loading!
viewModel.isLoading = false;
});
}
var invertPropertyTimer = $interval(viewModel.refreshView.bind(this), 3000);
}
})();
In your plunker it is not working because angular is refreshing the DOM after the $interval function finishes, and the value of isLoading
is still false.
As said in the ments the idea is to set the flag loading to false on the resolution of a promise.
If your refresh is made through HTTP and you are using $http service then that would be really easy as:
$http.get().then(function(){
viewModel.isLoading = false;
});
For the demo you wanted in your long operation I had to fake a asynchronous operation, I used $q
:
viewModel.DoSomeLongRunningOperation = function() {
var defered = $q.defer();
setTimeout(function(){ defered.resolve(); }, 300);
return defered.promise;
}
finally the interval part:
var invertPropertyTimer = $interval(function () {
viewModel.isLoading = true;
viewModel.DoSomeLongRunningOperation().then(function(){viewModel.isLoading = false;});
}.bind(this), 1000);
Here is a link to the plunker that "blink" as you want it: http://plnkr.co/edit/xsivUm7yVNeLEivQUoLM?p=preview
Try this
viewModel.isLoading = true;
$timeout(function(){
viewModel.refreshView();
viewModel.isLoading = false;
}, 0);