在Angular Service/Controller中实现安全$apply的最佳方法

Best approach to implement safe $apply in Angular Service/Controller

本文关键字:apply 最佳 方法 安全 实现 Service Angular Controller      更新时间:2024-05-20

我在Angular WebApp中使用socket.io客户端。我把它包装在一个服务中,但这可能不一定感兴趣。然而,由于传入的数据与使用该服务的控制器处于不同的范围,因此我总是必须调用$scope.$apply。更糟糕的是,我发现了一些情况(在连接/重新连接期间),我必须使用这里解释的safeApply

我知道这是一个角度反模式,但我看不到解决这个问题的方法。

有没有一种通用的方法来解决这个问题(最好是在服务内部),不会用大量的$scope.$apply/safeApply污染控制器?

BR,Daniel

这里也有一些代码,工作,但不好:

angular.module('mean')
    .controller('ConnectionStateController', function ($scope) {
        $scope.safeApply = function (fn) {
            var phase = this.$root.$$phase;
            if (phase == '$apply' || phase == '$digest') {
                if (fn && (typeof (fn) === 'function')) {
                    fn();
                }
            } else {
                this.$apply(fn);
            }
        };
        var socket = io.connect('http://localhost:3000');
        $scope.message = 'Not connected';
        socket.on('connect', function () {
            $scope.safeApply(function () {
                $scope.message = "Connected";
            });
        });
    });

在您的案例中,您知道socket.io操作总是异步运行,这使得它成为使用$scope.$apply的有效位置(位于页面底部)。

我建议不要使用safeApply,而是显式调用$scope.$apply。使用safeApply将掩盖实现中的真正错误。此外,如果您将代码封装在服务中,则可以在服务中注入$rootScope并调用$rootScope.$apply中的异步函数。那么您就不需要在每个控制器中调用$scope.$apply

我会自己回答这个问题,因为我有一个使用defer s的有效解决方案。然而,我不确定我做得是否正确。这是服务:

angular.module('mean')
    .factory('ConnectionService', function ($q, $timeout) {
        var bindings = {};
        var socket = io.connect('http://localhost:3000');
        // catch all events by overriding the socket's $emit
        var $emit = socket.$emit;
        socket.$emit = function (event, obj) {
            console.log('***', 'on', '"' + event + '"', obj);
            if (bindings.hasOwnProperty(event)) {
                bindings[event].notify(obj);
            }
            $emit.apply(socket, arguments);
        };
        return {
            socket: socket,
            observe: function (event, callback) {
                if (!bindings.hasOwnProperty(event)) {
                    bindings[event] = $q.defer();
                }
                bindings[event].promise.then(null, null, function (data) { 
                    callback.apply(this, [ data ]);
                });
            }
        };
    });

这允许控制器使用这样的服务:

ConnectionService.observe('news', function (data) {
    $scope.message = data;
});

不确定,这是一个好的解决方案,还是有副作用,我还没有看到。

BR,Daniel