19

I'm loading a template and then compiling it against my scope using $compile service.

var template = "<div> .. {{someCompilingStuff}} ..</div>";
var compiled = $compile(template)(cellScope);

and then using it in a popover

cellElement.popover({
    html: true,
    placement: "bottom",
    trigger: "manual",
    content: compiled
});

My template is quite complex and may take some time to compile.

How can I make sure that angular has finished compiling the template before using it in the popover ?

Edit: I tried to force angular to $apply() before creating the popover, it does work but generate javascript errors which is not acceptable for me.

Divya MV
  • 2,021
  • 3
  • 31
  • 55
kyori
  • 487
  • 1
  • 7
  • 21
  • but `$compile` service return `link` function, how you want you is as html? – Grundy Jul 24 '15 at 10:04
  • one way is to use $applyAsync or $evalAsync or $$postDigest – YOU Jul 25 '15 at 12:23
  • what is this `.popover`? Is this a bootstrap component? And if so, doesn't it expect either a string or a function, whereas you are passing an element (`compiled`) – New Dev Jul 26 '15 at 07:29
  • it's bootstrap popover's yes. And the compile function seems to return a DOM object witch is accepted by popover.content. My code is working but the popover is created with the uncompiled content, then the content compile and the text inside the popover is replaced as expected but because the lenght of the popover content changed I have visual bugs. That's why i would like to create the popover with the compiled content and not the template – kyori Jul 29 '15 at 07:41
  • @kyori, can you provide sample [plunker](http://plnkr.co/edit/?p=preview) with your code? – Grundy Jul 29 '15 at 08:07
  • also do you use [bootstrap](http://getbootstrap.com/) directly or something like [angular-bootstrtap](http://angular-ui.github.io/bootstrap/) or [angularStrap](http://mgcrea.github.io/angular-strap/)? – Grundy Jul 29 '15 at 08:11
  • i'll try to setup a fiddle later today, i'm using bootstrap because we are running an old version of angular-bootstrap with no support of HTML inside popovers, not in my power to upgrade angular-bootstrap .. – kyori Jul 29 '15 at 08:26
  • @kyori, can you reproduce? The only thing I managed to reproduce is a popover that changes its size when new content is added - a jarring effect, still, but not necessarily what you are seeing: http://plnkr.co/edit/7HBTrm3fR8vFYHRKip7W?p=preview – New Dev Aug 04 '15 at 17:54

6 Answers6

16

$compile allows you to use what is called a clone attach function which allows you to attach your elements to the document. Here is an example you may be able to use:

var template = "<div> .. {{someCompilingStuff}} ..</div>";
var compiled = $compile(template)(cellScope, function (clonedElement) {
  cellElement.popover({
    html: true,
    placement: "bottom",
    trigger: "manual",
    content: clonedElement
  });
});

Reference to Angular documentation about $compile usage

Ivar
  • 4,350
  • 2
  • 27
  • 29
Zachary Kuhn
  • 1,152
  • 6
  • 13
  • 1
    It allows you to attach a cloned node prior to its linking - exactly how does that address the OP's issue or answer the OP's question? – New Dev Aug 05 '15 at 05:12
  • I think it's important to note (considering the syntax), that $compile(arg) returns a function. This is why you can rely on this type of execution over a timeout... the callback is only executed after the $compile function completes. However, there is no promise (no pun intended) that the DOM has finished painting the template. – Joe Johnson Aug 05 '15 at 05:25
  • While this answer the question straight forward in this case it's not going to work (Read Simon Groenewolt's answer). Here is your example: http://jsfiddle.net/q47jbnq4/ – Amir Popovich Aug 05 '15 at 06:22
  • 7
    @JoeJohnson, unclear what you are saying. The `cloneAttach` (callback function, as you called it) executes *prior* to the link function returning the linked DOM element. The `$compile` function returns a function, right - the above-mentioned link function. Regardless, I can't see how this answer actually answer the question, so I'm puzzled by the upvotes – New Dev Aug 05 '15 at 18:24
  • I had a similar issue to the OP's issue. In my case, I was appending a compiled directive to a hidden div `#modal` before opening it, but sometimes the `#modal` would open be empty because the directive hadn't finished compiling. Using this method solves the problem by ensuring that the element is actually ready before opening the modal. – Daniel Bonnell Jan 19 '17 at 23:29
2

In these cases you probably would like to use $timeout to fix the issue.

Something like this:

var template = "<div> .. {{someCompilingStuff}} ..</div>";
var compiled = $compile(template)(cellScope);
cellElement.popover({
   html: true,
   placement: "bottom",
   trigger: "manual",
   content: compiled
 });

$timeout(function(){
    cellElement.popover("show");
}, 0);

Here's an example JSFIDDLE I've created for you. Look how I show & hide the popover using $timeout.

Amir Popovich
  • 29,350
  • 9
  • 53
  • 99
0

Even if it would be possible to get a notification when the compile/link function has completely rendered the template you still would not be certain if the browser did finish making changes to the page layout in response. You might be able to fix your problem by introducing a small delay using $timeout (setTimeout) before you trigger the popup. From reading the answers on is there a post render callback for Angular JS directive? I'd think that could fix your issue.

Community
  • 1
  • 1
Simon Groenewolt
  • 10,607
  • 1
  • 36
  • 64
  • 1
    Always avoid any timeout functionality unless you're delaying some type of animation. $timeout (like window.setTimeout) relies on a system's clock "speed" (processor). 500 milliseconds (milliseconds is the unit of measure for timeouts in JS / most code). In short, depending on how many applications are currently loaded into memory and running (including tabs in a browser, especially) on a user's computer, the variance of 500 milliseconds in a timeout is generally unpredictable. Hence, callbacks and promises. – Joe Johnson Aug 05 '15 at 05:22
  • @JoeJohnson -Using timeout is fine. There are places(like here) where $timeout(fn,0) is a good solution. Read this: http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful – Amir Popovich Aug 05 '15 at 06:26
  • 1
    That is a very, very old post. I disagree, regardless. Timeouts are unpredictable. – Joe Johnson Aug 05 '15 at 06:31
0

I'm going to supply a non-code answer (so you can dig a bit more and learn why -- plus I'm feeling lazy at this hour):

I'd recommend using event delegation with another parent HTML element as the delegate upon which you can invoke the popover method. Just include a class="popover" on any [parent] elements and compile a template within it. You may be able to execute / invoke the popover method on that parent element. However, I'm not entirely sure you don't need the child elements (from the template) since I don't know that plug-in. I would try anything other than a timeout, however.

My hope is that this answer (while possibly incorrect) precipitates a creative solution (resulting in clean, reliable code) from the author.

Joe Johnson
  • 1,814
  • 16
  • 20
0

As a suggestion you can create a compiler service in which inject $compile and create a method with parameter accepting html and scope.

in that method use $deferred and call that method from your controller and resolve will give you a call back which is a promise that compilation is done.

let me know if it dosn't works for you i will provide you code samples.

C For Code
  • 73
  • 10
-1
    var template = "<div> .. {{someCompilingStuff}} ..</div>";

cellElement.popover({
  html: true,
  placement: "bottom",
  trigger: "manual",
  content: function () {
    var html = "";
    $compile(template)(cellScope, function (clonedElement) {
      html = clonedElement;
    });
    return html;
  }

});