我如何知道范围更改何时传播到视图?

时间:2015-03-08 16:26:08

标签: angularjs

如果我在角度模型中执行变量赋值:

$scope.foo = 1;

如何基于我刚在模型上设置的新值知道视图何时完全更新?

(我之所以这样说是因为在实践中,我正在向$scope.foo提供一个相当大的数据结构,即使在强大的台式机上也需要几秒钟的时间来更新屏幕上的视图所以,我正在寻找方法知道什么时候我可以安全地从屏幕上删除加载动画

3 个答案:

答案 0 :(得分:1)

此答案假设延迟发生,因为摘要周期正在运行。如果范围更新正在等待异步操作,则来自@ New-Dev的答案是更正确的方法。

可能最好的方法是使用$timeout服务。有了它,您可以注册一个在当前摘要周期完成后运行的函数。你需要为delay参数指定零,为invokeApply参数指定true(或默认值),因为我假设你的加载动画是使用ngShow指令显示/隐藏的。

$timeout(function() { $scope.showLoadingAnimation = false; }, 0);

或者,您可以在范围上使用未记录的$$postDigest方法。在下一个摘要周期完成后,此方法将运行一次函数。请注意,此函数不会在$scope.$apply的上下文中运行,因此如果您希望AngularJS注意到范围更改,则需要自己调用$apply

$scope.$$postDigest(function() { 
  $scope.$apply(function() { $scope.showLoadingAnimation = false; });
});

(在此插入关于使用未记录的函数的标准免责声明。)

答案 1 :(得分:1)

"真实"回答这个问题,你不会喜欢的是:

"你不能"

当您在Angular中对模型进行更改时,您完全不知道需要多少$ digest()调用才能完全实现DOM。

让我们说你有一个非常大的阵容 - 它'有一百万个元素。

在您的标记中,您执行以下操作:

<div ng-repeat="foo in foos"><div ng-if="foo.somecondition">I'm a visible foo!></div></div>

在你的控制器中,你设置了foos:

.controller('myController', function($scope, $fooService) {
    $scope.foos = $fooService.get();
    $timeout(function(){
        //foo array done?  Not quite!!!!
    });
});

(在这个例子中,get()不是异步的,它会立即返回foos数组。)

运行控制器构造函数后,$ digest()将(最终)由Angular运行。这个摘要周期将在DOM中生成一百万行(这将花费很长时间并且将在修改DOM时锁定浏览器)。在摘要的最后,如果您在控制器中使用了$ timeout,那么$ timeout函数将会触发,但DOM尚未完全构建。

如果在那一秒检查DOM,你会发现你有百万行,但内部ng-if还没有被评估过。在第一个摘要周期结束时,Angular会注意到需要另一个摘要周期,它将直接运行下一个摘要周期 - 在评估$ timeout之后。

当然,这是一个相当人为的例子。如果您异步加载数据等,情况会更糟。

我建议真正的问题是你的foo数据太大 - 在不知道你的实现细节的情况下,我不能提供太多帮助来弄清楚如何更好地管理问题。

关键是在Angular中,你只关心模型的状态,而不是DOM的状态

如果我要尝试显示一百万个foos的数组,为了保持浏览器的响应,我会这样做:

.controller('myController', function($scope, $fooService) {
    $scope.loading = true;
    $scope.foos = [];

    var length = $fooService.length;  //1,000,000 foos
    var i = 0;

    var loadFoos = function() {
        var count = 0;
        while(count < 10) {
            $scope.foos.push($fooService.get(i + count++));
            if(i + count == length) {
                $scope.loading = false;  //we're done!
                return;
            }
        }
        i += count;
        //note, using setTimeout vs. $timeout deliberately - I want each digest cycle to completely finish and return the control of the execution context to the browser.
        setTimeout(function(){
            $scope.$apply(function() {
                loadFoos();
            }, 0);
        })
    };

    loadFoos();  //kick things off.

});

我正在做的是一次加载10个foos,然后在过渡期间将控制权返回给浏览器。您现在可以使用$ scope.loading标志来指示foos仍在加载,并且所有这些都可以。我把这个放在一起非常快,作为一个例子来说明问题 - 我确信有办法让它更优雅,甚至可能有一些语法错误。

如果我这样做&#34;对于真实的&#34;我可能会将此功能包含在服务本身中。

答案 2 :(得分:0)

如果以异步方式获取数据,则删除处理程序中的动画:

$scope.isLoading = true;
somSvc.loadData().then(function(data){
  $scope.foo = data;
  $scope.isLoading = false;
});

只要您将数据分配给范围变量,更改就会在下一个摘要周期中进行。

此分配需要一些时间,因此如果您需要事先在视图中更改某些内容,则需要将数据的分配延迟到$scope.foo,以便Angular有机会更改视图。

你不能在分配数据之前做到这一点,因为在更改分配数据的同一摘要周期中,Angular将忙于呈现DOM:

$scope.isLoading = true;
somSvc.loadData().then(function(data){
  $scope.isLoading = false;
  $scope.isRendering = true;

  $timeout(function(){
    $scope.foo = data;
    $scope.isRendering = false;
  }, 0);
});

http://plnkr.co/edit/KslF9lED0tkT7bKJ81LF?p=preview

编辑:如果你只有一个&#34; loading&#34;指标,然后上面的例子可以简化为:

$scope.isLoading = true;
somSvc.loadData().then(function(data){
  $scope.foo = data;
  $timeout(function(){
    $scope.isLoading = false;
  }, 0);
});