Re: [angular.js] Callback after directive is rendered.

24,888 views
Skip to first unread message

Vojta Jína

unread,
Jun 30, 2012, 6:16:53 PM6/30/12
to ang...@googlegroups.com
That's weird, can you create fiddle or plunker ?

Linking function should be called after the content is compiled/linked, so ti should work.
You can always do setTimeout or scope.$evalAsync(), however it should not be necesarry in this case.

V.

On Wed, Jun 20, 2012 at 10:18 AM, Nik <nik...@gmail.com> wrote:
Hi there, I 've just gotten my directive to pull in a template to append to its element like this:

# CoffeeScript
.directive 'dashboardTable', ->
  controller: lineItemIndexCtrl
  templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
  (scope, element, attrs) ->
    element.parent('table#line_items').dataTable()
    console.log 'Just to make sure this is run'

# HTML
<table id="line_items">
<tbody dashboard-table>
</tbody>
</table>

I am also using a jQuery Plugin called DataTables. The general usage of it is like this: $('table#some_id').dataTable(). You can pass in the JSON data into the dataTable() call to supply the table data OR you can have the data already on the page and it will do the rest.. I am doing the latter, having the rows already on the HTML page.

But the problem is that I have to call the dataTable() on the table#line_items AFTER DOM ready. My directive above calls the dataTable() method BEFORE the template is appended to the directive's element. Is there a way that I can call functions AFTER the append?

Thank you for your help!


--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To view this discussion on the web visit https://groups.google.com/d/msg/angular/-/Nds7-o0tQf8J.
To post to this group, send email to ang...@googlegroups.com.
To unsubscribe from this group, send email to angular+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/angular?hl=en.

Roy Truelove

unread,
Sep 13, 2012, 11:30:25 AM9/13/12
to ang...@googlegroups.com
I'm having this problem as well, and I saw that a few people shared it on Stack Overflow as well.

I created a fiddle to show as an example.

A basic HTML table created with ng-repeats:http://jsfiddle.net/roytruelove/cp6u6/1/
An attempt to datatable-ize that table: http://jsfiddle.net/roytruelove/cp6u6/3/

the datatable-izing must occur only after the ng-repeating is done, but how to trigger that?

Another option I could see is to not use ng-repeat and have the directive build the entire table every time $scope.data changes, but.. :-/

Godmar Back

unread,
Sep 13, 2012, 12:15:20 PM9/13/12
to ang...@googlegroups.com

In general, Angular does not support that, see https://github.com/angular/angular.js/issues/1306

In your example, I wouldn't be surprised if a solution could be found - perhaps a directive applied to an element that you know will be rendered after your table.

 - Godmar

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To post to this group, send email to ang...@googlegroups.com.
To unsubscribe from this group, send email to angular+u...@googlegroups.com.

Roy Truelove

unread,
Sep 13, 2012, 12:29:51 PM9/13/12
to ang...@googlegroups.com
I have a feeling that what I need to do is exactly what the ng-repeat directive does, by "preventing the compilation process form descending" into my nested directives.  Been staring at the ng-repeat code for an hour now with a puddle of drool on my desk..

Roy Truelove

unread,
Sep 13, 2012, 2:26:53 PM9/13/12
to ang...@googlegroups.com
Ok I'm putting the nail in the coffin on my work on this given Miško's comments on the issue above.  I don't believe that it is technically possible to know when DOM manipulation is complete, since it could be happening anywhere, by anyone, asynchronously, at any time.

I'm going to solve this problem heuristically by $watch-ing the data that triggers the DOM update and then checking the DOM to see if it looks ready for me to apply my changes.  Hacky, but hey.
Message has been deleted

Roy Choo

unread,
Sep 17, 2012, 4:21:22 AM9/17/12
to ang...@googlegroups.com
You could have a directive that checks if the last element had finished what it need to do.

like this?
http://jsfiddle.net/roychoo/cp6u6/5/

Regards
Roy

Roy Truelove

unread,
Sep 17, 2012, 11:15:49 AM9/17/12
to ang...@googlegroups.com
You could have a directive that checks if the last element had finished what it need to do.

Thanks Roy, this is a great suggestion, better than the way I've coded it for sure (I used setInterval).

I wonder, though, if it's bulletproof for the same reasons listed above?  For instance, if the directive that's in my <td> is complicated and takes a while to render, $scope.last will evaluate to 'true' before the child directive in td is completely rendered.  If the call to myTable.dataTable() needs it's td's to be complete, you might run into trouble.

Might be getting a bit contrived here but I think the point is an important one - that there's no deterministic way to ensure that DOM manipulation is complete.  (Right?)

One other side question if I may - I was surprised to see check-last would have access to ng-repeats '$last'.  That means 1) that they share the same scope, and 2) that ng-repeat must always happen before check-last.

I'm guessing that #2 is due to ng-repeat's high priority (1000), but I'd expect that for #1 that ng-repeat would have put $last in it's own scope, no?

Thanks again fellow Roy,
Roy


Roy Choo

unread,
Sep 18, 2012, 11:01:18 AM9/18/12
to ang...@googlegroups.com
Hi fellow Roy,


1) that they share the same scope, and
in the documentation, it is stated
 :

Special properties are exposed on the local scope of each template instance, including:

  • $index{number} – iterator offset of the repeated element (0..length-1)
  • $first{boolean} – true if the repeated element is first in the iterator.
  • $middle{boolean} – true if the repeated element is between the first and last in the iterator.
  • $last{boolean} – true if the repeated element is last in the iterator.


2) that ng-repeat must always happen before check-last.
Yes because of the high priority

Regards
Roy

john.fredr...@gmail.com

unread,
Sep 20, 2012, 10:25:32 PM9/20/12
to ang...@googlegroups.com

I have a much better solution.  

It involves modding angular.js to do what it already should be doing: providing a post render callback.

In the $scope.$apply method add one line of code (see the commented line below -  in angular-1.0.1.js its at line 7857).  


        $apply: function(expr) {
            try {
                beginPhase('$apply');
                return this.$eval(expr);
            } catch (e) {
                $exceptionHandler(e);
            } finally {
                clearPhase();
                try {
                    $rootScope.$digest();

                    // **** Broadcast to post render listeners ****
                    $rootScope.$broadcast("doneRender");

                } catch (e) {
                    $exceptionHandler(e);
                    throw e;
                }
            }
        },


Now in your directive's controller method, add a listener:

         controller: function ($scope, locale) {
                      // ...
           $scope.$on("doneRender", 
                       function() {
                               // Your directive's DOM will be completely rendered at this point. GUARANTEED. 
                       });


This is a robust solution.   You do not need any silly hack. (setTimeout: are you kiddin' me?)

If your squemish about modding angular.js, try to remember that there is a good reason it's called open source!

Roy Truelove

unread,
Sep 24, 2012, 8:39:24 AM9/24/12
to ang...@googlegroups.com, john.fredr...@gmail.com
Thanks John,

I think there was some discussion about this in the past - my understanding is that what you've done will almost always work, but cannot be guaranteed, and so has been explicitly left out so as not to be relied upon.  While Angular can know when it has finished calling all of the callbacks it has registered for manipulating the DOM and can broadcast on it (as you have done), there's no way to know that those functions themselves have completed their DOM manipulation as some of those things could be done asynchronously.

So if all of the callbacks are synchronous, or if they're async but fast enough it will work, but it will not always work.  I could envision some very frustrating bugs where it would work on a fast machine but not on a slower machine.

That's my understanding at least.  I wish I could find the old thread about this...

Godmar Back

unread,
Sep 24, 2012, 9:56:23 AM9/24/12
to ang...@googlegroups.com, john.fredr...@gmail.com
On Mon, Sep 24, 2012 at 8:39 AM, Roy Truelove <roytr...@gmail.com> wrote:
Thanks John,

I think there was some discussion about this in the past - my understanding is that what you've done will almost always work, but cannot be guaranteed, and so has been explicitly left out so as not to be relied upon.  While Angular can know when it has finished calling all of the callbacks it has registered for manipulating the DOM and can broadcast on it (as you have done), there's no way to know that those functions themselves have completed their DOM manipulation as some of those things could be done asynchronously.


Yes - but this problem is solvable by using a dependency queue (I typically my own implementation for such cases, but it may be possible by chaining promises).  The basic idea is that the 'rendering done' is broadcast only after all promises made during the rendering have been fully.  If any function that's doing DOM manipulation includes a possible asynchronous  call, it must add an unfulfilled promise to the chain that's fulfilled upon completion of the callback.  

This may require modifications to AngularJS internals, like perhaps the ng-include directive; and if there are external to AngularJS functions that manipulate the DOM it would require exposing an API to those functions that allow them to delay the firing of the rendering complete broadcast.

 - Godmar


Witold Szczerba

unread,
Sep 25, 2012, 11:27:13 AM9/25/12
to ang...@googlegroups.com
Hi,
the code with "doneRender" is not going to help with a "ng-repeat +
DataTables" combo, because ng-repeat, most of the time, will wait for
some http service to provide data, so look at this chain of events:

given such a markup:
<table my-data-table>
<tr ng-repeat="d in data"> ... </tr>
</table>
and controller:
$http.get('data').success(function(data) {
$scope.data = data
});

1. scope asks for data and waits until they arrive,
2. my-data-table directive registers listener on "doneRender",
3. ng-repeat starts watching "data" in scope,
4. nothing more to compile/execute, "doneRender" fires,
5. your directive catches "doneRenderer" and invokes elem.dataTable(),
6. DataTables consumes your empty table :(
6. nothing... still waiting for remote server,
7. data arrives, success callback in constructor fires,
8. it is too late, your table is consumed already.

Regards,
Witold Szczerba

Roy Choo

unread,
Sep 25, 2012, 12:48:39 PM9/25/12
to ang...@googlegroups.com
Hi,
Yes, what you said is true and i am supposing you mean done render as element.ready.

in the jsfiddle, i am checking if the element is ready (http://api.jquery.com/ready/) if DOM is not yet created,

the problem is this, the fiddle is checking on dom ready, if the data arrived after dom ready then the datatable won't be consumed.
thus i updated a bit.
http://jsfiddle.net/roychoo/cp6u6/12/ , to simulate that data comes after, you can click on the load data

 

Regards
Roy

limodou

unread,
Sep 29, 2012, 9:48:53 PM9/29/12
to ang...@googlegroups.com
On Wed, Sep 26, 2012 at 12:48 AM, Roy Choo <royc...@gmail.com> wrote:
Hi,
Yes, what you said is true and i am supposing you mean done render as element.ready.

in the jsfiddle, i am checking if the element is ready (http://api.jquery.com/ready/) if DOM is not yet created,

the problem is this, the fiddle is checking on dom ready, if the data arrived after dom ready then the datatable won't be consumed.
thus i updated a bit.
http://jsfiddle.net/roychoo/cp6u6/12/ , to simulate that data comes after, you can click on the load data

 

Regards
Roy



And I want to know if I can just use below code to fire callback after the dom rendered:

        $rootScope.$digest();
        if (callback) callback();
 
I tried above code, and don't see any problem now. So I want to know if this way can be accepted?

--
I like python!
UliPad <<The Python Editor>>: http://code.google.com/p/ulipad/
UliWeb <<simple web framework>>: https://github.com/limodou/uliweb
My Blog: http://my.oschina.net/limodou

Roman Elizarov

unread,
Feb 2, 2013, 3:18:16 PM2/2/13
to ang...@googlegroups.com
The scenario you've described clearly shows the problem that has to be solved. There is no way to rewrite all jQuery plugins like datatables into AngularJS from scratch, so there has to be a solution for their integration with AngularJS, or... how would you use AngularJS in a serious JS application? Of course, whatever 3rd party plugin you are integrating, it has to be able to "update" itself once DOM is changed by AngularJS (plugins that can consume DOM only once are clearly out of the question), but it also means that AngularJS has to be able to send notifications on DOM changes. Is there any progress in that direction? Can I $watchDomChanges? 

Peter Bacon Darwin

unread,
Feb 2, 2013, 4:30:24 PM2/2/13
to ang...@googlegroups.com
You can do things like:

scope.$watch(function() { return element.height(); });

But you can't guarantee that a digest is being called when the property changes.  You need some kind of trigger from the browser.


To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.

To post to this group, send email to ang...@googlegroups.com.

Wayne Hoover

unread,
Feb 2, 2013, 8:32:39 PM2/2/13
to ang...@googlegroups.com
I think most people solve this problem by using setTimout or $evalAsync, $watch can also be used in some cases. Basically setTimout and $evalAsync move the DOM manipulation code to the bottom the browsers event loop which always works for me.

Roy Truelove

unread,
Apr 11, 2013, 9:00:00 AM4/11/13
to ang...@googlegroups.com
This question was asked recently on stackoverflow and the author found what looks like a pretty elegant solution: http://stackoverflow.com/a/15946913/295797
Reply all
Reply to author
Forward
0 new messages