Javascript 角度:控制器可以监视服务器属性吗?
我有一个控制器,它管理I页数据,还有一个服务,它每30秒发出一次HTTP请求,以获取页面上显示的新数据。我试图以一种“角度”的方式来编写它,这种方式是可测试的,并且能够正确地利用服务 我可以想出两种基本方法,我猜其中一种(或者两者都有)是错误的:Javascript 角度:控制器可以监视服务器属性吗?,javascript,angularjs,design-patterns,promise,Javascript,Angularjs,Design Patterns,Promise,我有一个控制器,它管理I页数据,还有一个服务,它每30秒发出一次HTTP请求,以获取页面上显示的新数据。我试图以一种“角度”的方式来编写它,这种方式是可测试的,并且能够正确地利用服务 我可以想出两种基本方法,我猜其中一种(或者两者都有)是错误的: 控制器将数据存储在$scope变量中,并执行setInterval或$timeout调用服务的方法以获取新数据,然后更新变量 服务将数据存储在自己的变量/属性中,并定期调用自身以获取新数据。控制器以某种方式监视/侦听服务属性,以了解何时更新视图 对于这
setInterval
或$timeout
调用服务的方法以获取新数据,然后更新变量errorMsg
变量需要存在于某个地方。它是否应该存在于控制器中?在这种情况下,服务每次都需要返回该值。或者它应该存在于服务中,并且控制器不知何故监视它
我尝试了第一种方法,它似乎在控制器中产生了很多逻辑,主要是在遵循服务方法的
then()
s中。我的直觉是#2是正确的方法。但我有点不清楚管制员应该如何收听/观看服务。提前感谢。让我们从控制器的角度来看这一点:
控制器存储数据并查询服务
这称为拉动。您正在有效地创建在控制器中轮询的服务器响应流
服务存储数据,控制器监视数据
这称为推送。您正在有效地创建一个结果流,并将更改通知消费者,而不是让消费者查找更改
这两种方法都是解决您问题的有效方法。选择一种您认为更容易推理的方法。我个人同意第二个更干净,因为你不必在控制器中意识到它。这会让你大致了解:
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
属性。虽然Benjamin提供了一个非常好的解决方案,我完全同意他的回答,但我想补充一点,为了完整起见,对于您的问题,您可能还没有想到另一种选择:
使用WebSocket
使用websockets,您的服务器可以直接调用客户端,因此无需轮询更新。当然,这完全取决于您的场景和服务器技术。但这就是如何创建从服务器到客户端的真正推送
如果您想尝试一下:
如果您使用的是.Net服务器,请务必尝试。它使用起来非常好,并且有一个用于长轮询的回退机制
还有一个我没有真正使用过的图书馆,所以我不能说很多:
使用node.js时,您应该了解
关于实现,您可以同时执行这两种实现,但是您的第二个选项也可以更干净地使用web套接字。到目前为止,我一直是这样做的。我认为您应该将逻辑保留在控制器中(解决方案#1),因为否则,即使没有控制器需要,服务也会无限运行,这将导致整个应用程序的一些内存和网络开销。而且,我不认为逻辑对控制器来说太复杂 此外,将作用域函数传递给服务似乎并不那么优雅 我会这样做:
您可以创建简单服务(名为ServerStateService),它使用$interval每30秒从服务器获取一次刷新数据
$interval(function() {
$http.get(YOUR_URL_PATH).success(function(responseData) {
$rootScope.$emit('server.state.change', responseData);
});
}, 30 * 1000);
从服务器获得响应时,从名为“server.state.change”的ServerStateService发出或广播事件,并从服务器发送响应数据
最后使用角事件系统从控制器处理它
$rootScope.$on('server.state.change', function(data) {
//Do somethig with data
$scope.serverData = data;
});
虽然轮询服务器是在服务器和客户端之间共享状态的有效方法,但是您可以利用其他方法来提高服务器的性能并减少服务器的开销。上面提到的其中一个是WebSocket。然而,WebSocket是相当新的,它需要大量的服务器端工作来支持它们 其中一个鲜为人知或提及的方法叫做。它是HTML5标准的一部分,因此您会发现所有浏览器都实现了它(当然,除了IE惊喜!)。SSE对于服务器来说是一种非常简单的方式,可以提醒客户端状态发生变化。在我看来,“角度”的方式应该是编写指令 该指令将包含一个独立的作用域/子控制器。这将允许控制器和html模板文件之间的双向绑定 加载指令时,您将启动一个$timer对象,该对象定期调用内部控制器 函数,该函数将依次调用angular服务(服务器端数据的代理) 然后,来自服务的响应数据将用于更新绑定到html模板的控制器上的$scope变量 这还有一个优点,即允许您在通过di上的“$destroy”事件删除指令时取消计时器
$interval(function() {
$http.get(YOUR_URL_PATH).success(function(responseData) {
$rootScope.$emit('server.state.change', responseData);
});
}, 30 * 1000);
$rootScope.$on('server.state.change', function(data) {
//Do somethig with data
$scope.serverData = data;
});
'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) {
}
};
}
]);