我有一个管理I页面数据的控制器和一个每隔30秒发出一次HTTP请求的服务,以获取在页面上显示的新数据。我试图在" Angular"中写这个。可测试的方式并正确利用服务。
我可以想到两种基本方法,而我猜测其中一种(或两种)是错误的:
控制器将数据存储在$ scope变量中,并执行setInterval
或$timeout
调用服务方法以获取新数据,然后更新变量。
该服务将数据存储在其自己的变量/属性中,并定期调用其自身以获取新数据。并且控制器以某种方式监视/侦听服务属性以知道何时更新视图。
出于这个问题的目的,考虑一个具体的例子可能会有所帮助。如果HTTP请求失败,我想向视图/用户显示错误。假设一个errorMsg
变量需要住在某个地方。它应该存在于控制器中吗?在这种情况下,服务需要每次都返回该值。或者它应该存在于服务中,并且控制器以某种方式监视它。
我尝试过第一种方法,它似乎在控制器中产生了很多逻辑,主要是在跟随服务方法的then()
中。我的直觉是#2是正确的方法。但我有点不清楚控制器应该如何监听/观看服务。提前谢谢。
答案 0 :(得分:9)
让我们从控制器的角度来看待这个:
控制器存储数据并查询服务
这称为拉。您正在有效地创建一个服务器响应流,您将在控制器中进行轮询
服务存储数据,控制器观察它
这称为推送。您可以有效地创建结果流,并通知消费者更改而不是寻找更改。
这些都是解决您问题的有效方法。选择一个您更容易理解的方法。就个人而言,我同意第二个更清洁,因为您不必在控制器中了解它。这应该让你有一个大概的想法:
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
});
}
你可以像这样消费:
getServerState(function(state){
$scope.foo = state; //since it's in a digest changes will reflect
});
我们的最后一个问题是它泄漏了范围,因为代码不在控制器上,我们有一个回调注册到一个不再存在的范围。由于我们还没有使用有趣的ES6设施 - 我们必须提供一个"我已经完成了"处理getServerState方法的返回值,例如您在范围销毁事件上调用的.done
属性。
答案 1 :(得分:2)
虽然本杰明提供了一个非常好的解决方案,但我完全同意他的回答,我想补充一点,为了完整起见,你可能还没有想到另一个替代方案:
使用 websockets 。
使用websockets,您的服务器可以直接调用客户端,因此您无需轮询更新。当然,这取决于您的场景和服务器技术,这是否适合您。但这就是你如何从服务器到客户端创建一个真正的推送。
如果你想尝试一下:
如果您正在使用.Net服务器,那么绝对尝试SignalR。使用起来非常好,并且具有长时间轮询的回退机制。
还存在我没有真正使用的这个图书馆,所以我不能说很多:https://github.com/wilk/ng-websocket
使用node.js时,您应该查看Socket.IO
关于实现,您可以执行这两种实现,但是第二个选项也可能更清晰,也可以使用Web套接字。到目前为止,这是我以前的方式。
答案 2 :(得分:1)
我认为您应该将逻辑保留在控制器中(解决方案#1),否则即使没有控制器需要它,服务也将无限运行,这将导致整个应用程序的一些内存和网络开销。并且,我不认为逻辑对于控制器来说太复杂了。
此外,将范围函数传递给服务似乎并不那么优雅。
我做这样的事情:
factory('serverState', ['$http', function($http) { var update = function (){ return $http.get("serverUrl") } return { update:update }; }]);
function updateSuccess(res){ $scope.state = res.state; } function updateFail(res){ $scope.state = "Not connected"; } $interval(function() { serverState.update().then(updateSuccess,updateFail); }, 30000);
答案 3 :(得分:1)
您可以创建一个简单的Service(名为ServerStateService),它使用$ interval每30秒从服务器获取一次刷新数据。
$interval(function() {
$http.get(YOUR_URL_PATH).success(function(responseData) {
$rootScope.$emit('server.state.change', responseData);
});
}, 30 * 1000);
当您从服务器获得响应时,使用来自服务器的responseData从名为“server.state.change”的ServerStateService发出或广播该事件。
最后使用角度事件系统从控制器处理它。
$rootScope.$on('server.state.change', function(data) {
//Do somethig with data
$scope.serverData = data;
});
答案 4 :(得分:1)
虽然轮询服务器是在服务器和客户端之间共享状态的有效方法,但您可以利用其他方法来获得更好的性能并减少服务器的开销。其中一个是上面提到的WebSockets。但是,WebSockets相当新,需要大量的服务器端工作来支持它们。
其中一个鲜为人知或提到的方法称为Server Sent Events。它是HTML5标准的一部分,因此您会发现所有浏览器都实现它(当然,除了IE。惊喜!)。 SSE是服务器提醒客户端状态变化的一种非常简单的方法。
答案 5 :(得分:1)
在我看来," Angular"方法是写一个指令。
该指令将包含一个隔离的范围/子控制器。这将允许控制器和你的html模板文件之间的双向绑定。
当加载指令时,您将启动$ timer对象,该对象定期调用内部控制器 反过来调用角度服务的函数(服务器端数据的代理。)
然后,来自服务的响应数据将用于更新绑定到html模板的控制器上的$ scope变量。
这还有一个优点,即当通过' $ destroy'删除指令时,您可以取消定时器。关于指令范围的事件。
解决方案的大纲如下。
'use strict';
/*global angular,console,$:false*/
angular.module('testModule').
directive('testDirective', ['testService', function(testService) {
var testController = function($scope, testService) {
$scope.testData = {};
function serviceResponse(data) {
$scope.testData = data;
}
function serviceError(error) {
console.log(error);
}
var timeoutPromise = null;
function startTimer() {
timeoutPromise = $timeout(function() {
/*
* Call the service, which returns a promise that
* when resolved will follow the success or error path.
*/
testService.retrieveData().then(serviceResponse, serviceError);
}, 180000); // every 3 minutes
}
function cancelTimer() {
if (timeoutPromise !== null) {
$timeout.cancel(timeoutPromise);
}
}
$scope.$on('$destroy', function() {
cancelTimer();
});
// Start the timer
startTimer();
};
return {
restrict: 'EA',
scope: {},
templateUrl: 'testHTML.html',
controller: ['$scope', 'testService', testController],
link: function(scope, element, attrs) {
}
};
}
]);