我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。

我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:

错误:$digest已在进行中

有人知道如何避免这种错误或以不同的方式实现相同的事情吗?


当前回答

从最近与Angular团队就这个主题进行的讨论中可以看出:出于防将来的原因,您不应该使用$$phase

当被问及“正确”的做法时,答案是当前

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

我最近在编写angular服务来包装facebook、谷歌和twitter API时遇到了这种情况,这些API在不同程度上都有回调。

下面是服务中的一个示例。(为了简洁起见,服务的其余部分——设置变量、注入$timeout等——被省略了。)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

请注意,$timeout的延迟参数是可选的,如果未设置,则默认值为0($timeout调用$browser.deffer,如果没有设置延迟,则默认为0)

有点不直观,但这是写Angular的人的答案,所以这对我来说足够好了!

其他回答

了解到Angular文档将检查$$阶段称为反模式,我尝试使用$timeout和_.deffer来工作。

超时和延迟方法在dom中创建一个未解析的{{myVar}}内容的闪存,就像FOUT一样。对我来说,这是不可接受的。这让我没有太多教条地告诉我,有些东西是黑客,没有合适的选择。

每次唯一有效的方法是:

if(scope.$$phase!==“$digest”){scope.$digest()}。

我不明白这种方法的危险性,也不明白为什么评论中的人和棱角分明的团队会把它描述为黑客。命令看起来很精确,很容易理解:

“做摘要,除非已经发生了”

在CoffeeScript中,它更漂亮:

范围$digest(),除非作用域$$阶段为“$digest”

这有什么问题?有没有替代方案不会产生FOUT$safeApply看起来不错,但也使用$$阶段检查方法。

我对第三方脚本也有同样的问题,比如CodeMirror和Krpano,甚至使用这里提到的safeApply方法也没有为我解决错误。

但解决这个问题的方法是使用$timeout服务(不要忘记先注入它)。

因此,类似于:

$timeout(function() {
  // run my code safely here
})

如果在代码中使用

也许是因为它在工厂指令的控制器内,或者只是需要某种绑定,那么您可以执行以下操作:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

安全$apply的最短形式是:

$timeout(angular.noop)

当您收到此错误时,基本上意味着它已经在更新视图的过程中。您真的不需要在控制器中调用$apply()。如果您的视图没有按预期进行更新,并且在调用$apply()后出现此错误,那么很可能意味着您没有正确更新模型。如果你发布一些细节,我们可以找出核心问题。

        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

以下是避免此错误和避免$apply的好解决方案

如果基于外部事件调用,则可以将其与反跳(0)结合使用。以上是我们正在使用的“反跳”,以及完整的代码示例

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

以及代码本身,以侦听某些事件并仅在需要的$scope上调用$digest

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });