【问题标题】:DOM update after async call异步调用后的 DOM 更新
【发布时间】:2016-06-15 02:49:11
【问题描述】:

我正在从我的服务器获取一些数据并通过 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“事件”中的每个 DOM 元素不应该自动更新吗? $scope.$apply() 不起作用,也不应该是必需的。这里有什么问题?

【问题讨论】:

    标签: javascript html angularjs dom


    【解决方案1】:

    在 Angular 中进行 DOM 操作的情况下,这是众所周知的。

    看到这个SO link

    【讨论】:

      【解决方案2】:

      在 angularjs 中每隔一段时间就会需要$timeout。很可能是为了初始化一个jQuery plugin

      您的错误行:

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

      是因为您的 DOM 尚未设置,而您正试图在 DOM。

      当您使用$timeout 时,它应该在 Angular 操作 DOM 之后运行,并且在浏览器渲染之后运行(在某些情况下这可能会导致闪烁)。这就是为什么当您设置$timeout

      如果您想了解有关摘要循环的更多信息。你也应该知道$evalAsync

      • 如果代码使用指令中的 $evalAsync 进行排队,它应该在 Angular 操作 DOM 之后但在浏览器呈现之前运行
      • 如果代码使用控制器中的 $evalAsync 进行排队,它应该在 Angular 操作 DOM 之前(以及在浏览器呈现之前)运行——你很少需要这样
      • 如果代码使用 $timeout 进行排队,它应该在 Angular 操作完 DOM 并且在浏览器渲染之后运行(在某些情况下可能会导致闪烁)。

      此外,Angularjs 是一个 javascript 框架。浏览器几乎必须同时完成许多事情,其中​​之一就是执行 JavaScript。但是 JavaScript 经常使用的一件事是要求浏览器构建一个显示元素。这通常被认为是同步完成的(特别是当 JavaScript 不是并行执行时),但不能保证确实如此,并且 JavaScript 没有明确定义的等待机制。

      解决方案是“暂停” JavaScript 执行以让渲染线程赶上。这就是setTimeout() 的超时时间为 0 的效果。它就像 C 中的线程/进程 yield。虽然它似乎在说“立即运行”,但它实际上让浏览器有机会完成一些非 JavaScript 的事情,这些事情在处理这段新的 JavaScript 之前一直在等待完成.

      (实际上,setTimeout() 在执行队列的末尾将新的 JavaScript 重新排队。请参阅 cmets 以获得更长解释的链接。)

      IE6 恰好更容易出现此错误,但我已经看到它出现在旧版本的 Mozilla 和 Firefox 中。

      此外,关于为什么使用 $timeout 有时会派上用场,已经写了很多文章并进行了解释。

      您可以在其中找到很好的解释的链接:

      【讨论】:

        【解决方案3】:

        拆分操作

        要做的事情之一是通过链接它们来分解操作。

        $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 服务返回承诺,它可以用于链接。

        $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 链。可以创建任意长度的链,并且由于可以使用另一个 Promise 来解决一个 Promise(这将进一步推迟其解决方案),因此可以在链中的任何点暂停/推迟对 Promise 的解决。这使得实现强大的 API 成为可能。1


        重构设计

        AngularJS 是一个 MVW* 框架。

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

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

        【讨论】:

          猜你喜欢
          • 2015-11-29
          • 1970-01-01
          • 1970-01-01
          • 2010-09-14
          • 1970-01-01
          • 1970-01-01
          • 2017-03-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多