2

I am having a problem updating css on an element in Angular. I have a div that contains elements generated by ng-repeat. The div containing these elements has a set height and has the ng-scrollbars directive attached to it. ng-scrollbars creates a nice scrollbar inside a div when the elements in the div exceed the height of the their container. To do this it creates nested internal divs. So in this case, if the height of the nested internal divs is larger than their container then a scrollbar is made so that you can scroll through the elements.

So lets say I add or remove items to the ng-repeat list. When this happens I would like additional css to be added to the top level div only if the internal div made by ng-scrollbars exceed the height of the top level div. (the height of the internal div is the actual height of the ng-repeat list because it contains the ng-repeat elements)

To do this I created two directives, one that broadcasts an event and one that receives it. The one that broadcasts the event is attached to every ng-repeat element and is broadcasted when the element is created or destroyed. This is to check when changes happen to the ng-repeat list.

The one that receives the broadcast is attached to the top level div container (the one that contains the div made by ng-scrollbar) and checks its height against the internal div made by ng-scrollbar, if the height of the internal div is greater than the top level div then add some css.

I'm sorry if this seems confusing, here's my code doing this with comments attached that I hope explain it better:

app.directive('gtScrollLb', function () {
  return {
    restrict: 'A',
    link: function (scope, elem, attrs) { //elem is the top level container of the ng-repeat elements
      var scrollDiv = elem.children()[0]; //this is an internal div created by ng-scrollbars
      var anotherScrollDiv = $(scrollDiv).children("#" + $(scrollDiv).attr("id") + "_container"); //another internal div created by ng-scrollbars
      var id = attrs.gtScrollLb;

      scope.$on(id + "-repeat-change",
        function(newVal, oldVal) {
            //check if ng-scrollbar div that now contains the ng-repeat elements
            //has a greater height than the top level container
            if ($(anotherScrollDiv).height() >= elem.height()) {
              elem.css("border-bottom", "1px solid darkgrey");
            } else {
              elem.css("border-bottom", "");
            }
        }
      );
    }
  };
});

app.directive('gtRepeatable', function () {
  return {
    restrict: 'A',
    link: function (scope, elem, attrs) {
      var id = attrs.gtRepeatable;
      scope.$emit(id + "-repeat-change"); //event that item has been added by ng-repeat
      var parent = scope.$parent;
      elem.bind("$destroy",function(){
        parent.$broadcast(id + "-repeat-change");//event that item has been destroyed by ng-repeat
      });
    }
  }
});

The problem is that the ng-repeat elements are added and destroyed before the height of their internal container is determined again. So the height being checked when the change event is fired is actually the old height of the internal container.

I need to somehow check the new height. I have been trying to solve this for a long time now, any help would be greatly appreciated.

Edit

In a nutshell, I need an even to fire or $watch to happen after ng-repeat has finished and the DOM has rendered. The problem I'm having here only happens because the event is fired before the DOM is rendered.

Edit 2

For @vittore who flagged this as a duplicate. If you had actually read my question in detail you would see that the one you proposed as the duplicate had nothing to do with my actual problem. There is no problem with ng-scrollbars here. ng-scrollbars is working fine. But it was necessary to include it in the description because it effects how I'm going about solving my problem.

Graham
  • 5,488
  • 13
  • 57
  • 92
  • 1
    Create a demo that replicates problem – charlietfl Jan 14 '16 at 17:41
  • @charlietfl the code surrounding this problem is to much to try and replicate in a jsFiddle. Is there anything in particular I can elaborate on? – Graham Jan 14 '16 at 18:05
  • if we can't see it, or replicate it, how can anyone help? – charlietfl Jan 14 '16 at 18:21
  • @charlietfl I'll see what I can do. But heres basically what I need to do. I need an event to fire after ng-repeat has finished and the dom has updated. After the dom has updated is the most important part. – Graham Jan 14 '16 at 18:46
  • use $last within items to know when you get to end – charlietfl Jan 14 '16 at 18:49
  • @charlietfl $last isn't needed here because I setup an event thats firing when the ng-repeat item is created and destroyed. $last wouldn't give the desired effect of detecting when the ng-repeat list has changed. Also, last happens before the DOM is rendered. I need something happening AFTER the DOM has rendered. – Graham Jan 14 '16 at 18:51
  • in that case without a demo it's not clear what the problem is – charlietfl Jan 14 '16 at 18:54
  • can you create an [mcve] in a plunker? – blurfus Jan 14 '16 at 18:57
  • Obviously I really need to do this so you guys can see what I'm talking about so I'll try to get one going – Graham Jan 14 '16 at 19:18
  • Possible duplicate of [ng-scrollbar is not working with ng-repeat](http://stackoverflow.com/questions/24663917/ng-scrollbar-is-not-working-with-ng-repeat) – vittore Jan 14 '16 at 23:07
  • @vittore this is not a repeat. Nothing is wrong with ng-scrollbar. – Graham Jan 14 '16 at 23:43

2 Answers2

0

There is a way to get event when last loop element was renderd. Check this link: AngularJS - Manipulating the DOM after ng-repeat is finished

Also here is exact question you have : ng-scrollbar is not working with ng-repeat

and important part:

  $scope.$on('loopLoaded', function(evt, index) {
    if (index == $scope.me.length-1) {
      $scope.$broadcast('rebuild:me');
    }
  });
Community
  • 1
  • 1
vittore
  • 17,449
  • 6
  • 44
  • 82
  • That is not the exact question.... my ng-scrollbar works fine. Please read my original question and you will see it doesn't have anything to do with ng-scrollbar malfunctioning. – Graham Jan 14 '16 at 23:42
  • @Graham have you tried the code snippet I included? it shows how to broadcast event AFTER last element is rendered, all you have left to do is listen to it with `$scope.$on` – vittore Jan 15 '16 at 13:39
  • The **Decouple it** part of the post may work... but it also might not. For my specific case I fired events on every `ng-repeat` creation and destruction. So it essentially did the same thing as using `$last`. I solved my problem with MutationObservers and am managing them correctly by only activation them right before the change has to be made and then having them deactivate themselves after they make the change. This leaves no overhead. And was relatively simple. – Graham Jan 15 '16 at 16:58
0

To get this to work I ended up using MutationObservers. I had to use two, one for attribute changes and one for childList changes.

MutationObservers should be used sparingly, they will slow down your app and cause serious bugs if left unchecked. I accidentally caused an infinite loop doing an async call.

It's best to only connect them right before the change to the DOM is made and have them disconnect themselves once they make the change.

Graham
  • 5,488
  • 13
  • 57
  • 92
  • Not the best way, but couldn't you have checked the height after some $timeout? – Jānis Jan 15 '16 at 07:38
  • @John, sure, and risk having the css load late so that it appears to have lag to the user. Or risk having it checked too early and then the css doesn't load at all. MutationObservers are perfectly fine if you manage them correctly like I described here. $timeout is the developer being lazy in my opinion. – Graham Jan 15 '16 at 16:27