Angular:控制器可以监视服务器属性吗?

时间:2015-02-04 23:14:38

标签: javascript angularjs design-patterns promise

我有一个管理I页面数据的控制器和一个每隔30秒发出一次HTTP请求的服务,以获取在页面上显示的新数据。我试图在" Angular"中写这个。可测试的方式并正确利用服务。

我可以想到两种基本方法,而我猜测其中一种(或两种)是错误的:

  1. 控制器将数据存储在$ scope变量中,并执行setInterval$timeout调用服务方法以获取新数据,然后更新变量。

  2. 该服务将数据存储在其自己的变量/属性中,并定期调用其自身以获取新数据。并且控制器以某种方式监视/侦听服务属性以知道何时更新视图。

  3. 出于这个问题的目的,考虑一个具体的例子可能会有所帮助。如果HTTP请求失败,我想向视图/用户显示错误。假设一个errorMsg变量需要住在某个地方。它应该存在于控制器中吗?在这种情况下,服务需要每次都返回该值。或者它应该存在于服务中,并且控制器以某种方式监视它。

    我尝试过第一种方法,它似乎在控制器中产生了很多逻辑,主要是在跟随服务方法的then()中。我的直觉是#2是正确的方法。但我有点不清楚控制器应该如何监听/观看服务。提前谢谢。

6 个答案:

答案 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),否则即使没有控制器需要它,服务也将无限运行,这将导致整个应用程序的一些内存和网络开销。并且,我不认为逻辑对于控制器来说太复杂了。

此外,将范围函数传递给服务似乎并不那么优雅。

我做这样的事情:

  1. 服务:
  2. factory('serverState', ['$http', function($http) {
        var update = function (){
           return $http.get("serverUrl")
        }
       return {
        update:update
       };
     }]);
    
    1. 控制器:
    2.     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) {
    }
  };
}
]);