异步调用后DOM更新

时间:2016-03-02 10:07:09

标签: javascript html angularjs dom

我从服务器获取一些数据并通过Angular双向绑定更新DOM。但它没有按预期工作,我需要将它包含在一个丑陋的setTimeout函数中,以便DOM有时间更新。

$http.post('myBackend.php', someData)
.then(function(res){
    $scope.data = res.data;
    doStuffWithDOMElements(); // Does not work
});

虽然这有效:

 $http.post('myBackend.php', someData)
 .then(function(res){
     $scope.someDataToPopulateDOMwith = res.data;
     setTimeout(function(){ doStuffWithDOMElements();}, 50); // Yup, works
 });

在没有超时的情况下给出错误的行"无法读取null" 的属性是:

let y_0 = document.getElementById("l_0").offsetTop;

和我的index.html

<div id="l_{{$index}}" ng-repeat = "x in data"></div>

这很奇怪。不应该包含在Angular&#34; events&#34;中的每个DOM元素。自动更新? $scope.$apply()不起作用,也不是必需的。这里有什么问题?

3 个答案:

答案 0 :(得分:1)

The need of $timeout comes every once in a while in angularjs.Most probably to init a jQuery plugin.

Your Error line:

 let y_0 = document.getElementById("l_0").offsetTop; 

is because your DOM has not yet been set and you are trying to get the element which yet has to be set or rather rendered in the DOM.

When you use $timeout,it should run after the DOM has been manipulated by Angular, and after the browser renders (which may cause flicker in some cases).That is why it is working in your case when you set the $timeout.

If you want to learn more about digest cycle. Your should know about $evalAsync as well.

  • If code is queued using $evalAsync from a directive, it should run after the DOM has been manipulated by Angular, but before the browser renders
  • If code is queued using $evalAsync from a controller, it should run before the DOM has been manipulated by Angular (and before the browser renders) -- rarely do you want this
  • If code is queued using $timeout, it should run after the DOM has been manipulated by Angular, and after the browser renders (which may cause flicker in some cases).

FurtherMore, Angularjs is a javascript framework. A browser has to do a number of things pretty much all at once, and just one of those is execute JavaScript. But one of the things JavaScript is very often used for is to ask the browser to build a display element. This is often assumed to be done synchronously (particularly as JavaScript is not executed in parallel) but there is no guarantee this is the case and JavaScript does not have a well-defined mechanism for waiting.

The solution is to "pause" the JavaScript execution to let the rendering threads catch up. And this is the effect that setTimeout() with a timeout of 0 does. It is like a thread/process yield in C. Although it seems to say "run this immediately" it actually gives the browser a chance to finish doing some non-JavaScript things that have been waiting to finish before attending to this new piece of JavaScript.

(In actuality, setTimeout() re-queues the new JavaScript at the end of the execution queue. See the comments for links to a longer explanation.)

IE6 just happens to be more prone to this error, but I have seen it occur on older versions of Mozilla and in Firefox.

Also, a lot has been written and explained about why the use of $timeout comes in handy time to time.

Links where you will find good explanation:

答案 1 :(得分:1)

分解操作

其中一件事就是通过链接分解操作。

$http.post('myBackend.php', someData)
    .then (function onFulfilled (response) {
        $scope.someDataToPopulateDOMwith = response.data;
        return response;
    }).then (function onFulfilled2 (response) {
        doStuffWithDOMElements();
    });

这允许$q服务在调用第二个履行处理程序之前执行摘要循环。 AngularJS框架需要进行摘要循环,因此ng-repeat指令的监视处理程序有机会更新DOM。 ng-repeat指令需要在doStuffWithDOMElements函数可以安全地操作DOM之前完成DOM的更新。

使用$timeout服务

避免使用原始浏览器setTimeout功能,而是使用$timeout服务。然后,AngularJS $q服务会自动执行摘要周期。

由于$timeout服务返回promise,它可以用于链接。

$http.post('myBackend.php', someData)
    .then (function onFulfilled (response) {
         $scope.someDataToPopulateDOMwith = response.data;
         return response;
    }).then (function onFulfilled2 (response) {
         //return $timeout promise
         return $timeout(function(){return response}, 1000);
    }).then (function onFulfilled3 (response) {
         doStuffWithDOMElements();
    });

因为调用promise的then方法会返回一个新的派生promise,所以很容易创建一个promise链。可以创建任何长度的链,并且由于可以使用另一个承诺(将进一步推迟其解析)来解决承诺,因此可以在链中的任何点暂停/推迟承诺的解析。这使得实现强大的API成为可能。 1

重新设计设计

AngularJS是一个MVW *框架。

避免以编程方式操作HTML DOM:操作HTML DOM是AJAX应用程序的基石,但它很麻烦且容易出错。通过声明性地描述UI在应用程序状态发生变化时应如何更改,您可以从低级DOM操作任务中解脱出来。使用Angular编写的大多数应用程序永远不必以编程方式操作DOM,尽管如果您愿意,也可以。 2

了解doStuffWithDOMElements()函数的作用,并创建自定义指令以观察该模型并更新DOM。这更适合AngularJS框架,并将避免这些时间问题。

答案 2 :(得分:0)

如果角度操作DOM,则众所周知。

请参阅此SO link