I have a controller which manages I page of data and a service which makes an HTTP request every 30 seconds to get fresh data to show on the page. I'm trying to write this in an "Angular" way that is testable and leverages the service properly.
I can think of two basic approaches, and I'm guessing one (or maybe both) is wrong:
The controller stores the data in a $scope variable, and does a setInterval
or $timeout
to call methods of the service to get new data and then update the variable.
The service stores the data in it's own variables/property and periodically calls its self to get new data. And the controller somehow watches/listens to the service properties to know when to update the view.
For the purposes of this question, it may be helpful to consider a concrete example. If the HTTP request fails, I want to show the error to the view/user. So assume an errorMsg
variable that needs to live somewhere. Should it live in the controller? In which case, the service needs to return that value every time. Or should it live in the service, and the controller somehow watches for it.
I've tried the first approach, and it seems to result in a LOT of logic in the controller, mostly in then()
s which follow the service methods. My instinct is that #2 is the correct way to do it. But I'm a little unclear as to how the controller should listen/watch the service. Thanks in advance.
Let's look at this from the controller's point of view:
The controller stores the data and queries the service
This is called pull. You are effectively creating a stream of server responses which you're polling in the controller
The service stores the data and the controller watches it
This is called push. You're effectively creating a stream of results and notifying the consumer of changes rather than it looking for them.
Those are both valid approaches for your issue. Pick the one that you find easier to reason about. Personally I agree that the second is cleaner since you don't have to be aware about it in the controller. This should get you a general idea:
function getServerState(onState){
return $http.get("/serverstate").then(function(res){
onState(res.data);// notify the watcher
}).catch(function(e){/*handle errors somehow*/})
.then(function(){
return getServerState(onState); // poll again when done call
});
}
Which you can consume like such:
getServerState(function(state){
$scope.foo = state; //since it's in a digest changes will reflect
});
Our last issue is that it leaks the scope since the code is not on the controller we have a callback registered to a scope that'll cease to exist. Since we can't use fun ES6 facilities for that yet - we'll have to provide a "I'm done" handle to the getServerState method return value, for example a .done
property which you'll call on a scope destroy event.
While Benjamin provided a very nice solution and I totally agree with his answer I would like to add, for the sake of completeness, that there is an other alternative for your problem you maybe did not think of yet:
Using websockets.
With websockets your server can call the client directly so you do not need to poll for updates. Of course it depends on your scenario and server technologies whether this would be an option for you at all. But that's how you can create a true Push from the server to the client.
If you want to give it a try:
If you are working with .Net servers, then absolutely try SignalR. It is very nice to use and has a fall-back mechanism to long-polling.
There also exists this libray which I did not really use, so I cannot tell a lot: https://github.com/wilk/ng-websocket
Working with node.js you should have a look at Socket.IO
Regarding the implementation you can do both implementation but your second option might be cleaner also with web-sockets. That is the way I used to do it so far.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With