20
votes

I'm new to all Angular World, and i'm facing a problem managing the directives.

I'm working on a project that uses Tabs, and i want to extend its functionality to handle overflowed tabs when window size is narrower that the width of all my tabs, so i need to calculate some dimensions of elements to achieve this. The tabs are built from an Object in $scope, the problem is that the directive that calculates the dimensions is run before the view is fully compiled.

Plnkr Link: http://plnkr.co/edit/LOT4sZsNxnfmQ8zHymvw?p=preview

What i've tried:

  1. loading the template in directive using templateUrl
  2. working with transclude
  3. to use $last in ng-repeat
  4. try to reorder directive and create a dummy directive in every tab to trigger an event

I think that there is some AngularJs event, or property to handle this situation.

Please Guys Help :)

4
what's wrong with the $timeout you are using? I have been using similar directive, and using 0 for delay, seems to give browser a chance to paint the DOM before code runs in directive - charlietfl
you are right, but this is workaround not a fix for the problem, when you have complicated directive to build this will end by using timeout multiple times and leads to a mess !! - Abu Qusai
is a common usage... get used to it - charlietfl
if we simply 'got used to it' for all our computation.. we'd still be typing machine code. - Alexander Mistakidis
I need to back this up too. I am inserting directives into a view and they render immediately but the dynamic classes do not. Worse still you get intermediate classes such as your-class-name-add during the adding process. I have experimented with $timeout and I'll need to add a delay as long as 500ms to cover these changes - which is not reliable. The below answer by Alexander represents the most elegant method so far but involves yet another watch... Its becoming unmanageable. - sidonaldson

4 Answers

8
votes

I make a branch on your plunker, I I think this is what you were looking for

I change your directive to this

.directive('onFinishRenderFilters', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                });
            }
        }
    }
});

Then on your HTML I added the directive

 <li ng-repeat="tab in tabs" on-finish-render-filters>

And the last thing I put the code I want to run after the repeat finish

$scope.$on('ngRepeatFinished', function (ngRepeatFinished) {
        $scope.tabs = [{
            index: 1,
            title: "Tab 1",
            link: "/tab1/"
        },{
            index: 2,
            title: "Tab 2",
            link: "/tab2/"
        },{
            index: 3,
            title: "Tab 3",
            link: "/tab3/"
        },{
            index: 4,
            title: "Tab 4",
            link: "/tab4/"
        }];

  });

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

6
votes

You can add a watch on the actual DOM elements in order to know when your ng-repeat is loaded into the DOM.

Give your ul an id, say #tabs

$scope.$watch(
    function () { return document.getElementById('tabs').innerHTML },  
    function(newval, oldval){
        //console.log(newval, oldval);
        //do what you like
    }, true);
6
votes

AngularJS does not seem to have reliable post-render callbacks for directives used within ng-repeat. [1]

Maybe you can solve this on CSS level by adding a "responsive" overflow-control element for specific screen widths.

  1. https://groups.google.com/forum/#!topic/angular/SCc45uVhTt8

Update: There now is a way to do this using nested $timeouts. See: http://lorenzmerdian.blogspot.de/2013/03/how-to-handle-dom-updates-in-angularjs.html

3
votes

According to your comments, you already have a solution that works ... and its currently the only solution that I know of: $timeout.

The question is, what do you want to achieve? You want a callback when all rendering is done. Ok, good, but how can angular know this? In a modern app which you are apparently developing when using angular, a rerender can happen all time! A js event can occur where data is loaded from backend, even right after initializing the app, which leads to rerendering. A mouse movement by the user can trigger rerendering of elements, which are even outside of angulars scope because of CSS rules. Any time, a rerender can happen.

So, with these considerations what can angularJS tell you about when rendering is done? It can only tell you, when current $apply and/or $digest chain is processed aka the browser event queue is empty for now. Exactly that is the only information that angularJS knows of. And you use $timeout with a delay of 0 for this.

Yes, you are right, in a bigger appliataion, that can be tricky to a point where its not reliable anymore. But in this case, you should think about how you can solve this in another way. F.i. if a rerender happens later, there must be a cause, like new data that is loaded from the backend. If so, you know when the data is loaded and therefore a rerender happens and therefore you know exactly when you have to update your widths.