Javascript 角度:控制器可以监视服务器属性吗?

Javascript 角度:控制器可以监视服务器属性吗?,javascript,angularjs,design-patterns,promise,Javascript,Angularjs,Design Patterns,Promise,我有一个控制器,它管理I页数据,还有一个服务,它每30秒发出一次HTTP请求,以获取页面上显示的新数据。我试图以一种“角度”的方式来编写它,这种方式是可测试的,并且能够正确地利用服务 我可以想出两种基本方法,我猜其中一种(或者两者都有)是错误的: 控制器将数据存储在$scope变量中,并执行setInterval或$timeout调用服务的方法以获取新数据,然后更新变量 服务将数据存储在自己的变量/属性中,并定期调用自身以获取新数据。控制器以某种方式监视/侦听服务属性,以了解何时更新视图 对于这

我有一个控制器,它管理I页数据,还有一个服务,它每30秒发出一次HTTP请求,以获取页面上显示的新数据。我试图以一种“角度”的方式来编写它,这种方式是可测试的,并且能够正确地利用服务

我可以想出两种基本方法,我猜其中一种(或者两者都有)是错误的:

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

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

  • 对于这个问题,考虑一个具体的例子可能是有帮助的。如果HTTP请求失败,我想向视图/用户显示错误。因此,假设一个
    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) {
        }
      };
    }
    ]);