简体   繁体   English

异步调用后DOM更新

[英]DOM update after async call

I am fetching some data from my server and updating the DOM through Angular two way bindning. 我正在从服务器中获取一些数据,并通过Angular两种方式绑定来更新DOM。 It is however not working as expected and I need to wrap it inside an ugly setTimeout function for the DOM to have time to update. 但是,它不能按预期方式工作,我需要将其包装在丑陋的setTimeout函数中,以便DOM有时间更新。

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

while this works: 虽然这有效:

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

The line that gives error without the timeout "Can't read propertly of null" is this: 在没有超时的情况下给出错误的行“无法正确读取null”是这样的:

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

and in my index.html 在我的index.html中

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

This is weird. 真奇怪 Shouldn't every DOM element that is wrapped up in Angular "events" be updated automatically? 是否应该自动更新包含在Angular“事件”中的每个DOM元素? $scope.$apply() does not work and shouldn't be necessary either. $scope.$apply()不起作用,也不应该是必需的。 What's wrong here? 怎么了

The need of $timeout comes every once in a while in angularjs.Most probably to init a jQuery plugin . $ js偶尔会出现$ timeout的需要,这很可能是用来初始化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 . 这是因为尚未设置DOM,而您正在尝试获取尚未设置或在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 . 当您使用$timeout ,它应该在Angular操纵DOM之后以及浏览器渲染之后运行(在某些情况下可能会导致闪烁)。这就是为什么在设置$timeout时它可以在您的情况下运行的原因。

If you want to learn more about digest cycle. 如果要了解有关摘要循环的更多信息。 Your should know about $evalAsync as well. 您还应该了解$ evalAsync

  • 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 如果使用指令中的$ evalAsync将代码放入队列中,则该代码应在Angular处理DOM之后但在浏览器呈现之前运行
  • 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 如果使用$ evalAsync从控制器中将代码放入队列中,则它应在Angular操作DOM之前(以及在浏览器呈现之前)运行-您几乎不需要这样做
  • 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). 如果代码使用$ timeout排队,则应在Angular操作DOM之后和浏览器渲染之后运行代码(在某些情况下可能导致闪烁)。

FurtherMore, Angularjs is a javascript framework. 此外,Angularjs是一个JavaScript框架。 A browser has to do a number of things pretty much all at once, and just one of those is execute JavaScript. 浏览器几乎必须一次完成许多事情,其中​​之一就是执行JavaScript。 But one of the things JavaScript is very often used for is to ask the browser to build a display element. 但是JavaScript经常使用的一件事是要求浏览器构建显示元素。 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. 通常认为这是同步完成的(特别是因为JavaScript不是并行执行的),但不能保证是这种情况,并且JavaScript没有明确的等待机制。

The solution is to "pause" the JavaScript execution to let the rendering threads catch up. 解决方案是“暂停” JavaScript执行,以使渲染线程赶上来。 And this is the effect that setTimeout() with a timeout of 0 does. 这就是setTimeout()的超时值为0的效果。 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. 就像C中的线程/进程产量一样。尽管似乎说“立即运行”,但实际上,它使浏览器有机会完成一些非JavaScript的事情,而这些事情在加入此新的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.) (实际上, setTimeout()在执行队列的末尾重新排队新的JavaScript。有关更多说明的链接,请参见注释。)

IE6 just happens to be more prone to this error, but I have seen it occur on older versions of Mozilla and in Firefox. IE6恰好更容易出现此错误,但是我已经看到它发生在旧版本的Mozilla和Firefox中。

Also, a lot has been written and explained about why the use of $timeout comes in handy time to time. 此外,关于为什么使用$ timeout有时会派上用场,对此已经写了很多文章并作了解释。

Links where you will find good explanation: 可以找到很好解释的链接:

Breakup the Operations 分手运营

One of the things to do is breakup the operations by chaining them. 要做的事情之一是通过链接将操作拆分。

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

This allows the $q service to execute a digest cycle before invoking the second fulfillment handler. 这允许$q服务在调用第二个实现处理程序之前执行摘要周期。 The AngularJS framework needs to do a digest cycle so the watch handlers for the ng-repeat directive have an opportunity update the DOM. AngularJS框架需要执行一个摘要循环,因此ng-repeat指令的监视处理程序有机会更新DOM。 The ng-repeat directive needs to finish updating the DOM before the doStuffWithDOMElements function can safely manipulate the DOM. ng-repeat指令需要先完成DOM的更新,然后doStuffWithDOMElements函数才能安全地操作DOM。


Use $timeout Service 使用$timeout服务

Avoid using the raw browser setTimeout function, instead use the $timeout service. 避免使用原始浏览器setTimeout函数,而应使用$timeout服务。 The AngularJS $q service then automatically does digest cycles. 然后,AngularJS $q服务会自动执行摘要循环。

Since the $timeout service returns promises, it can be used for chaining. 由于$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();
    });

Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. 因为调用promise的then方法会返回新的派生promise,所以很容易创建promise的链。 It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. 可以创建任何长度的链,并且由于一个承诺可以用另一个承诺来解决(这将进一步推迟其解决方案),因此可以在链中的任何点暂停/推迟对承诺的解决。 This makes it possible to implement powerful APIs. 这样就可以实现功能强大的API。 1 1


Re-factor the Design 重构设计

The AngularJS is a MVW* framework. AngularJS是MVW *框架。

Avoid manipulating HTML DOM programmatically: Manipulating HTML DOM is a cornerstone of AJAX applications, but it's cumbersome and error-prone. 避免以编程方式操纵HTML DOM:操纵HTML DOM是AJAX应用程序的基石,但它既麻烦又容易出错。 By declaratively describing how the UI should change as your application state changes, you are freed from low-level DOM manipulation tasks. 通过声明性地描述UI应如何随着应用程序状态的变化而变化,使您摆脱了底层DOM操作任务的束缚。 Most applications written with Angular never have to programmatically manipulate the DOM, although you can if you want to. 尽管可以,但大多数使用Angular编写的应用程序都不需要以编程方式操作DOM。 2 2

Look into modeling what the doStuffWithDOMElements() function does and creating custom directives to watch that model and update the DOM. 研究对doStuffWithDOMElements()函数的功能进行建模,并创建自定义指令以监视该模型并更新DOM。 This fits better with the AngularJS framework and will avoid these timing issues. 这更适合AngularJS框架,并且可以避免这些计时问题。

in case of DOM manipulation in angular,it is well known. 在以角度操作DOM的情况下,这是众所周知的。

see this SO link 看到这个SO链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM