Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS $timeout isn't waiting for UI-Router to finish rendering before computing values

I'm using angularJS and requirejs via angularAMD to wire together a complex application.

One of my states has two simple views like so:

  $stateProvider
    .state("common", {
      url: "/",
      views: {
        "view1": {
          templateUrl: "view1.tpl.html"
          },
        "view2": {
          templateUrl: "view2.tpl.html"
        }
      }
    });

html

<div ui-view="view1"></div>
<div ui-view="view2"></div>

View1 has a directive:

  .directive('checkViewOneBoxWidth', function ($timeout) {
    return {
      restrict: 'A',
      link: function (scope, elem, attrs) {
        var linkFunctionAfterDomComplete = function () {
          console.log(elem[0].scrollWidth);
        }
        $timeout(linkFunctionAfterDomComplete, 0);
      }
    }
  })

And view2 has a stylesheet that applies some styling to the page, including changing the height and width of the view1 container.

I want the function linkFunctionAfterDomComplete to compute the element's height and width properties after the DOM has been modified by the stylesheet in view2, so I wrapped the linkFunctionAfterDomComplete function in a $timeout().

The console.log from checkViewOneBoxWidth here is reporting the size of this element, before the styling from the stylesheet in view2 modified the size of this element, despite the fact that we would expect the $timeout to cause the linkFunctionAfterDomComplete function to calculate the size of this element after styling from view2 is applied and the DOM is complete.

How can I get linkFunctionAfterDomComplete to calculate element properties that reflect those which reflect the true state of the element after it had been fully rendered and all styling has been applied?

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

like image 810
nikk wong Avatar asked Nov 09 '15 22:11

nikk wong


1 Answers

You need to pass a function to $timeout:

$timeout(function() {
  console.log('timeout: view 1 linked')
}, 0);

Or:

var fn = function () {
  console.log('timeout: view 1 linked');
};

$timeout(fn, 0);

The following:

$timeout(console.log('timeout: view 1 linked'), 0);

Will execute the console.log instantly then pass the return value (undefined) as the first argument to $timeout.

Demo: http://plnkr.co/edit/zWgoJihcMH693fWfUp09?p=preview

Not sure if you have the same problem in your real application, but thought I should post this anyway.


New answer based on edit

The problem is that your link element is located in the view template instead of in the index.html.

If you move it to index.html it will work as you expect (and you don't need the $timeout in this case, unless your real application adds custom HTML from the directive):

Demo: http://plnkr.co/edit/m3sriF2YYH5T8y42R5Lr?p=preview

If you keep the link in the view template and keep the timeout, this is basically what happens:

  1. Angular will run its compile function to prepare all the bindings and the views.
  2. The HTML from the views will be inserted into the DOM, the directives will execute and the console.log will be added to the timeout queue.
  3. The link element has now been inserted into the DOM and the browser will start retrieving the CSS. The console.log in the timeout queue will execute.

Note that the fetching of the CSS is asynchronous and the console.log from the timeout queue will execute before the fetching and parsing of the CSS has finished.

Also note that you might see different behaviors in different browsers.

There are solutions, but it might get really messy.

To read more about it:

When is a stylesheet really loaded?

I would recommend to just move the link to the index.html.

like image 61
tasseKATT Avatar answered Sep 30 '22 15:09

tasseKATT