0

I am using AngularJS with ng-repeat directive.
I really need to know if there is a way to bind events like 'ng-repeat-before-render' and 'ng-repeat-after-render'. I need that for several reasons, one of them is in order to calculate the height before and after the rendering.
I saw several directives that rely on $scope.$last, but $scope.$last is true only when rendering the last element in sequence and in my case I add elements in the middle of the list.

I really appreciate a direction to solve this issue.

Naor
  • 23,465
  • 48
  • 152
  • 268
  • what is the requirement.. may be you really wont need before-after render – harishr Jul 28 '14 at 11:59
  • @HarishR I have a list of items and a scrollbar (using jquery perfect scrollbar). Scrolling up in the list loads more items (up, not down as people used to). After rendering the messages, I update the scrollbar. The problem is that the scrollbar remains at the top of the list instead moving to the middle. This issue may be solved without ngRepeat events but I really do like to know what options ngRepeat give in order to do things after render. – Naor Jul 28 '14 at 12:11
  • see pre/post link functions : http://stackoverflow.com/questions/18297208/post-link-vs-pre-link-in-angular-js-directives – Jscti Jul 28 '14 at 12:11
  • you have ng-repeat-start and ng-repeat-end where you can wire up the things – harishr Jul 28 '14 at 12:13
  • @HarishR How can I use it? Can you add an example? – Naor Jul 28 '14 at 12:25
  • @Bixi How is pre/post link functions can help me? – Naor Jul 28 '14 at 12:26

2 Answers2

1

Use $evalAsyc for before render, and $timeout for after render:

link: function(scope, element,attr){
        scope.$evalAsync(function(){
               // executes after compile/link
               // and before render
        })
        $timeout(function(){
               // executes after render
        })
})
Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • The link function is happened for each item in the repeater. I am looking for single "before" and single "after". If I have 20 elements, those methods will run 20 times. – Naor Jul 28 '14 at 13:19
  • Create a parent directive outside of your ng-repeat. In angular, there is no explicit before render and after render events. But $evalAsync and $timeout will accomplish the same thing – Michael Kang Jul 28 '14 at 13:27
  • $evalAsync is actually a really good answer. It's just that it doesn't solve the "only one time" part of the request. But you can do that with a simple debounce mechanism and you're home... – Chad Robinson Jul 28 '14 at 15:41
  • @ChadRobinson What do you mean with "It's just that it doesn't solve the only one time part"? – Naor Jul 29 '14 at 06:06
  • The `$evalAsync` is in the directive so it will get run once for each entry in your ngRepeat. In your OP you asked for a solution that only got executed one time. – Chad Robinson Jul 29 '14 at 11:19
0

Unfortunately, ngRepeat doesn't provide what you're asking for. ngRepeatStart/End are just markers where the sequence starts/ends, not where the rendering starts/ends. (It's used for things like making headers/footers out of some of the rows in a set, or maybe simple pagination.)

The real problem with rendering is that you never know where you actually are in the rendering chain when you're in Angular. A directive like the one you want would be difficult/impossible to even create. When Angular starts rendering, it starts "walking" the DOM, looking at every item/child and "compiling" those elements as it goes. At any time, transclusion operations can cause it to start a whole new sequence. You see this a lot with custom element-type directives.

Angular ends up doing this in two passes: compile and link. It's not until you get to the link stage that you could even dream of calculating the height of an item, because that's where the objects are actually added to the DOM. But during this stage, you're still on the main thread (JS has only one) so the browser probably hasn't had a chance to calculate it yet!

My suggestion would be to change tactics. Use some sort of debounce and setImmediate facility to let the processing loop finish what it's doing, and then do any of your size-checking updates required at that point. You can easily detect when an element needs this done with a simple directive - any directive with ANY link function at all will have that get called when the item is rendered.

So my suggestions is:

  1. Write a "iWasDrawn" directive with a simple link function.
  2. In that link function, call a 'debounce' or 'setImmediate' function with a debounce - pick your choice, there are dozens.
  3. In that callback, do your size calculation.

This should be efficient, it should work, and it is pretty simple.

Chad Robinson
  • 4,575
  • 22
  • 27
  • I thought about using a debounce and even did it. The problem in such solution is to find the exact time for the debounce function. Less time will cause incorrect callback execution. More time will cause jumping ui. – Naor Jul 29 '14 at 06:04
  • Take a look at [setImmediate](https://github.com/YuzuJS/setImmediate), a plugin that helps address this. It's a super fast callback and you can pair it up with clearImmediate to debounce (clearImmediate followed by setImmediate will clear previous but not-yet-executed calls, then let you set one last one). – Chad Robinson Jul 29 '14 at 11:22
  • It's a philosophical difference. AngularJS was founded on the principle that such a heavy focus on direct DOM manipulation was inefficient - a hack in its own right. The core devs tried their best to eliminate the need to do this. Avoiding jQuery isn't about jQuery itself - it's about the need to do things like $('.go-find-my-element').doSomethingToIt(). Your problem is back in that land they were trying to get away from, so a hack may be unavoidable. – Chad Robinson Jul 30 '14 at 13:11