27

I have an Angular directive to handle Bootstrap popovers as shown in the code below. In my directive I'm setting the popover content to a HTML string, which I think is ugly. What I wanna do is to use an "template.html" file instead of HTMLstring. In that way I will be able to use the same directive with different template files depending on which type of popover I wanna show. That's my plan anyway.

So, how do I in the best way load html code from my template.html and use it instead of the HTMLstring in the AngularJs directive below?

app.directive('mypopover', function ($compile) {

var HTMLstring = "<div><label class='control-label' style='color: rgb(153, 153,153)'>Search</label>&nbsp;&nbsp;"+"<input placeholder='Search assignment' ng-model='searchText' type='text' class='form-control'> <br>"+"<label class='control-label' style='color: rgb(153, 153, 153)'>Select an assignable</label>"+"<p ng-repeat='p in projects | filter:searchText'ng-click='createEvent(user.id,date)'>"+"{{p.title}}</p></div>";

var getTemplate = function (contentType) {
    var template = '';
    switch (contentType) {
        case 'user':
            template = HTMLstring;
            break;
    }
    return template;
}
return {
    restrict: "A",
    link: function (scope, element, attrs) {
        var popOverContent;
        if (scope.user) {
            var html = getTemplate("user");
            popOverContent = $compile(html)(scope);                    
        }
        var options = {
            content: popOverContent,
            placement: "right",
            html: true,
            date: scope.date
        };
        $(element).popover(options);
    },
    scope: {
        user: '=',
        date: '='
    }
};
});
0xRLA
  • 3,279
  • 4
  • 30
  • 42

2 Answers2

20

A quick solution is using templateCache with inline template:

Inline template:

<script type="text/ng-template" id="templateId.html">
      This is the content of the template
</script>

Js:

app.directive('mypopover', function ($compile,$templateCache) {

    var getTemplate = function (contentType) {
        var template = '';
        switch (contentType) {
            case 'user':
                template = $templateCache.get("templateId.html");
                break;
        }
        return template;
    }

DEMO

If you need to load external templates, you need to use ajax $http to load the templates manually and put in the cache. Then you can use $templateCache.get to retrieve later.

$templateCache.put('templateId.html', YouContentLoadedUsingHttp);

Sample code:

var getTemplate = function(contentType) {
    var def = $q.defer();

    var template = '';
    switch (contentType) {
      case 'user':
        template = $templateCache.get("templateId.html");
        if (typeof template === "undefined") {
          $http.get("templateId.html")
            .success(function(data) {
              $templateCache.put("templateId.html", data);
              def.resolve(data);
            });
        } else {
           def.resolve(template);
        }
        break;
    }
    return def.promise;
  }

DEMO

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • This works perfect, however is it possible to use angularjs expressions into the template?? I mean something like this: – joselo Feb 25 '14 at 02:43
  • 3
    @joselo: Yes, it is. In this case we have to compile it `popOverContent = $compile("
    " + popOverContent+"
    ")(scope);`. See demo http://plnkr.co/edit/CoDdWWQz8jPPM4q1mhC5?p=preview
    – Khanh TO Feb 25 '14 at 13:25
  • In the above case, it is assumed that the content of popover is fixed(template.html). What if I want to use different content for different popovers? Something like an attribute passed from html to show different template. `a href pop-over items="items" title="someTitle" template="location/of/anotherTemplate.html" >Show Another Pop over` In this case, $templateCache won't store the second template. Loading over $http doesn't help being asynchronous. – Aniket Sinha Aug 02 '14 at 12:13
  • @Aniket Sinha: You could load `location/of/anotherTemplate.html` using $http or use inline template. Then handle the click event of the `` to display the popover. – Khanh TO Aug 02 '14 at 12:27
  • 1
    @Aniket Sinha: See my updated answer. Hope it helps. – Khanh TO Aug 02 '14 at 12:48
  • Can I achieve 2 way data binding in the above case? I want the popover to contain some input fields which will send data back to the parent. I don't want to create a service to share data. Is there anyway I can send the data back to parent? – Aniket Sinha Aug 05 '14 at 11:38
  • @Aniket Sinha: the OP case works with jQuery, maybe you could look at http://angular-ui.github.io/bootstrap/ – Khanh TO Aug 05 '14 at 13:04
  • The problem with AngularUI popover is there inability for custom template as well as HTML popovers. The above solution worked for me the way you suggested. Only if I could achieve 2 way data binding with it. – Aniket Sinha Aug 05 '14 at 15:57
  • @Aniket Sinha: In that case, you just need to bind with the parent scope: http://plnkr.co/edit/ueu0DCsXOzhi9G6vAP0n?p=preview . But I don't recommend you to do so because it makes your code difficult to reuse, your template is tightly coupled to your parent scope. – Khanh TO Aug 09 '14 at 02:10
  • I agree with you about the reusability. I tried to `$watch` the object inside popover. But the problem there is that once you close the popover, you lose the binding. Similarly, in your plunk, once I close the popover, the binding is lost. – Aniket Sinha Aug 11 '14 at 07:46
  • @Aniket Sinha: I'll check. Anyway, I don't recommend binding directly like this. – Khanh TO Aug 11 '14 at 14:29
  • Ha, I removed the binding entirely. Now I have a different controller for popup, which gets input from user. On clicking the submit button, it `$emit` and on the parent controller I use `$on`. Works just fine except that once popover is closed and reopened, the popover inputs/submit doesn't work. – Aniket Sinha Aug 12 '14 at 07:34
  • I was trying to implement the soln where instead of removing tooltip from DOM, we can apply css to `display:none`. Something like given here http://plnkr.co/edit/d73cqVAsRMiAjHh6lkXw?p=preview . See line 1867 and 1869 in bootstrap-ui.js. Can we do something like this ? – Aniket Sinha Aug 12 '14 at 10:32
  • @Aniket Sinha: I did not use bootstrap popover before. But there could be a problem with the OP's code: the code inside the link function is run just `once` and the popover is `not re-created` from second time. You could try http://angular-ui.github.io/bootstrap/ – Khanh TO Aug 14 '14 at 13:21
  • As a matter of fact, I did try angular-ui's popover. But they don't support HTML templates(See https://github.com/angular-ui/bootstrap/issues/220) which was the basic reason I tried your approach. I ended up using ngStrap's popover. Appreciate your help. – Aniket Sinha Aug 15 '14 at 16:03
4

To complete the answer from Khahn, if you load a dynamic template, the last part should look like that:

return {
restrict: "A",
scope: {
    item: "=" // what needs to be passed to the template
},
link: function(scope, element, attrs) {

  getTemplate("user").then(function(popOverContent) {

    var options = {
      content: $compile($(popOverContent))(scope),
      placement: "bottom",
      html: true,
      trigger: "hover"
    };
    $(element).popover(options);

  });
}

};

Kourkis
  • 57
  • 3