6

I'm creating a AngularJS directive that is supposed to have a C3.js-based chart in it. Problem is that the C3 library does not see the DOM element it's supposed to attach to. The directive's link function looks something like this:

link: function(scope, element, attrs) {
    scope.someid = 'id';
    scope.chart = c3.generate({
        bindto: "#somechart"+scope.someid,
        data: {
            columns: [
                ['data1', 30, 200, 100, 400, 150, 250],
                ['data2', 50, 20, 10, 40, 15, 25]
            ]
        }
    });         
    console.log($("#somechart"+scope.someid).size()); // this is a test, outputs 0
}

The template for the directive has this in it:

<div id="#somechart{{ scope.someid }}">...</div>

The problem is that the c3.generate()'s bindto does not see the #somechartid. The console.log() I've put in outputs 0 which means the element is not present in the DOM at the moment when the link function is called.

If I call the same chart-generating code from the browser's console or even from some ng-click the chart gets rendered.

Is there a way to overcome this problem without using a solution like $timeout?

UPDATE 2014-07-15 15:33 Changed the code sample and added relevant line from directive's template.

  • 1
    please can you be a little more specific as to why the #somechartid cannot be seen by the link function? is it loaded on a template which causes delay or is the #somechartid generated dynamically after the page has been loaded? – DineshKP Jul 14 '14 at 19:49
  • The directive's template is provided by `templateUrl`. The DOM element in question is present in the template, it's `id` attribute is specified as `id="#somechart{{ data.id }}`. – Chief Crocodile Wrestler Jul 15 '14 at 08:13
  • does you id attribute actually contain a # within it? id="#somechart{{data.id}}". Another thing I notice is that your id attribute is being set dynamically by angular but you are referencing a "#somechartid" which actually might not be existing on your page. – DineshKP Jul 15 '14 at 10:15
  • The id on the page does not contain the `#`. That was a typo, sorry, fixed it. I've updated the code and included relevant line from the template. The issue here is that I'm not able to initiate the C3 graph in the link function since the graph `div` is not yet in the DOM. If I try to do it later by some `ng-click` or sth it works. – Chief Crocodile Wrestler Jul 15 '14 at 13:35
  • Possible duplicate of [DOM is not ready in a directive's link function. Is hacky Timeout the only solution?](https://stackoverflow.com/questions/28422752/dom-is-not-ready-in-a-directives-link-function-is-hacky-timeout-the-only-solut) – Johann Sep 27 '17 at 18:03

3 Answers3

1

Use $timeout function in your link function if you want to manipulate dom, which is not yet generated. See this

Jan Barta
  • 450
  • 2
  • 8
  • I mentioned I didn't want to use `$timeout` since this seems like a hackish solution. – Chief Crocodile Wrestler Jul 14 '14 at 19:33
  • Ups, sorry, missed that. So I'm also interested in solution – Jan Barta Jul 14 '14 at 19:39
  • 2
    Turns out that the `$timeout` and `$evalAsync` are solutions to this problem afterall. @Teq1 mentioned a question that led me to [this](https://stackoverflow.com/questions/17301572/angularjs-evalasync-vs-timeout) and [this](https://stackoverflow.com/questions/17225106/anyway-to-trigger-a-method-when-angular-is-done-adding-scope-updates-to-the-dom/17239084#17239084). The discussions there explain the problem, the solution and the specifics of using `$evalAsync` and `$timeout`. – Chief Crocodile Wrestler Jul 15 '14 at 14:08
0

Have you tried something like this

link: function(scope, element, attrs) {
    scope.chart = c3.generate({
        bindto: element[0],
        data: {
            columns: [
                ['data1', 30, 200, 100, 400, 150, 250],
                ['data2', 50, 20, 10, 40, 15, 25]
            ]
        }
    });         
}
Ali Nouman
  • 3,304
  • 9
  • 32
  • 53
  • It's a very similar approach to the one suggested by Teq1 but it's not working. I'm interested in a "native" Angular solution that would allow me to trigger this when the DOM is ready. – Chief Crocodile Wrestler Jul 15 '14 at 06:43
0

Maybe usage of element.find('#id') will help:

link: function(scope, element, attrs) {
    var item = element.find('#somechartid');
    scope.chart = c3.generate({
        bindto: item,
        data: {
            columns: [
                ['data1', 30, 200, 100, 400, 150, 250],
                ['data2', 50, 20, 10, 40, 15, 25]
            ]
        }
    });         
}
Teq1
  • 631
  • 1
  • 6
  • 20
  • Tried it, didn't work. I've peeked at the C3 source and they use D3's `select()` which in turn uses `querySelector()`. Anyhow, I'm more interested in finding out if there is a native solution in Angular that will allow me to trigger events after the elements are in DOM. – Chief Crocodile Wrestler Jul 15 '14 at 06:40
  • Check solutions from this question: http://stackoverflow.com/questions/12240639/angularjs-how-can-i-run-a-directive-after-the-dom-has-finished-rendering Probably classic $(window).load(function() will do the work. – Teq1 Jul 15 '14 at 07:26
  • 1
    ok, I've checked out the the link you provided. The `$evalAsync` solves this! Problems and answers discussed [here](https://stackoverflow.com/questions/17301572/angularjs-evalasync-vs-timeout) and [here](https://stackoverflow.com/questions/17225106/anyway-to-trigger-a-method-when-angular-is-done-adding-scope-updates-to-the-dom/17239084#17239084) explain the problem, the solution and the specifics of using `$evalAsync` and `$timeout`. – Chief Crocodile Wrestler Jul 15 '14 at 14:04