6

Is it possible to compile this html template string:

"<p>List of products from {{supplier.name}}</p>
<p ng-repeat="ref in refs">{{ref}}</p>"

directly to an html string like:

"<p>List of products from Some Supplier</p>
<p>a0120</p>
<p>a0241</p>
<p>z1242</p>
<p>z3412</p>"

or at least the less clean version:

"<p class="ng-scope ng-binding">List of product from Duval</p>
<!-- ngRepeat: ref in refs track by $index -->
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">a0120</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">a0241</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">z1242</p>
<p ng-repeat="ref in refs track by $index" class="ng-scope ng-binding">z3412</p>"

I tried using $compile(templateStr)($scope) but the dom elements returned are not fully processed. However I managed no compile it to a page element using the following directive and and inspecting that element I can see it has the final html I'm looking for:

app.directive('compile', function($compile) {
    return{
        restrict: 'A',
        scope: {
            compile: '=compile',
            data: '=ngData'
        },
        link: function(scope, element, attrs) {
            scope.$watch('data',
                    function(value) {
                        for (var k in scope.data)
                            scope[k] = scope.data[k];
                    }
            )

            scope.$watch('compile',
                    function(value) {
                        element.html(value);
                        var a = $compile(element.contents())(scope);
                    }
            )
        }
    }
})

Is there any way I can get that final html directly from the template? Thanks

PS: What I'm trying to achieve here is to edit a template directly in CKEditor (in text mode, not source) and only eventually goint to source mode to add some "ng-repeat" attributes. Using template engines like Handlebars require placeholders outside html elements and are automaticaly erased by CKEditor since it only deals with html.

POSSIBLE SOLUTION (hacky): One possible way is to use the compile directive on an hidden element and read the element's content after view is loaded on the controller:

$scope.$on('$viewContentLoaded', $scope.onLoaded);
$timeout(function() {
    var el =$("#text div")[0]
    cleanAngularStuff(el)
    $scope.currMailTemplate.processed = el.innerHTML
});

The cleanAngularStuff function is just to clean extra angular directives and classes.

I'll post it here if someone wants to use it or improve it.

Any better way to do this without adding an element to the page?

Community
  • 1
  • 1
RuiFortes
  • 183
  • 4
  • 13
  • I tried that but I want to use an ckeditor (or similar) to edit the template and these editor only allow html elements. Addind {{#each ref}} outside an element tag is automatically erased – RuiFortes Oct 16 '13 at 11:58
  • @RuiFortes it depends on when you want what's been compiled. What are you trying to achieve by having the html output of angular? – Mathew Berg Oct 16 '13 at 12:06
  • just added some explanation to original post – RuiFortes Oct 16 '13 at 12:24
  • I don't really understand why you're binding to `'compile'` on your scope... is there some compile object on your controller's scope? And what does `$scope.data` look like in your controller? I guess just some more context would be helpful. – tennisgent Oct 16 '13 at 14:05
  • @tennisgent compile is a directive. See http://docs.angularjs.org/api/ng.$compile I just change it to use a isolated scope. I also used some hacky strategy to extract passes "data" object properties to the scope root just so I don't have to prefix all template placeholders – RuiFortes Oct 16 '13 at 14:21
  • RuiFortes did u get a solution to this. I also need the output as a string – parliament Apr 09 '15 at 16:06

2 Answers2

4

What you need to do is access the compiled element after a $digest cycle.

So within a $digest cycle you can do:

templateString = '<some-template-code/>';
...
var compiled = $compile(templateString)(scope);
// scope.$digest // only call this if not within a $digest cycle

// you can do a $timeout to let the previous digest cycle complete
$timeout(function(){
  var theHtml = compiled[0].outerHTML;
  console.log('the html with the variables', theHtml);
});

If you aren't already within a digest cycle then you need to manually call scope.$digest(). You can see the embedded sample below.

(function(){
  "use strict";
  var app = angular.module('soApp', []);
  
  app.directive('showCompiledTemplate',[
            '$compile','$timeout',
    function($compile , $timeout) {
      return {
        restrict: 'E',
        template: '<div class="compiled-template">' +
           '<div><textarea cols="40" rows="15" readonly></textarea></div>' +
             '<div class="output"></div>' +
                  '</div>',
        scope: {
          data: '=',
          template: '='
        },
        link: function(scope,elem,attrs) {
            var textarea = elem.find('textarea')[0];
            var output = elem.children().children().eq(1);
   var updateOutput = function(tpl) {
                var compiled = $compile(tpl)(scope);
                $timeout(function(){
                    var theHtml = compiled[0].outerHTML;
                    textarea.value = theHtml;
                    output.html(theHtml);
                });
            };
            scope.$watch("template",function(tpl){
    updateOutput(tpl);
            });
            scope.$watch("data",function(){
                updateOutput(scope.template);
            },true);
        }
      };
    }
  ]);
  
  app.controller('MainCtrl', function() {
    this.data = {
      name: 'John',
      list: ['one duck','two ducks','three ducks']
    };
    //this.template = "<div>hi</div>";
    var template = '';
    template += '<div>\n';
    template += '  <p>{{data.name}}</p>\n';
    template += '  <ul>\n';
    template += '    <li ng-repeat="item in data.list">{{item}}</li>\n';
    template += '  </ul>\n';
    template += '</div>\n';
    this.template = template;
  });
  
 })();
.form-field {
    padding-bottom: 10px;
}
.form-field span {
    width: 70px;
    display: inline-block;
}
.compiled-template {
   display: -webkit-flex;
   display: flex;
   -webkit-flex-direction: row;
   flex-direction: row;
}
.compiled-template textarea {
    background-color: #eee;
    margin-right: 10px;
}
.compiled-template .output {
    border: 1px solid #ccc;
    padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<div ng-app="soApp">
  <div ng-controller="MainCtrl as main">
      <div class="form-field">
          <span class="form-label">Name:</span>
          <input type="text" ng-model="main.data.name" /> <br/>
      </div>
      <div class="form-field">
          <span class="form-label">Template:</span>
          <textarea ng-model="main.template" cols="40" rows="8"></textarea> <br/>
      </div>
      <div>
          <show-compiled-template data="main.data" template="main.template" />
      <div>
  </div>
</div>
JoseM
  • 4,302
  • 2
  • 24
  • 35
  • this should be marked as the correct answer. I was running into issues using $compile to convert an HTML string with Angular expression, compile it, then turn it back into an HTML string that is populated with data. Before using $timeout the Angular brackets {{}} were still showing up, this fixes it! thanks! – officert Jul 15 '16 at 01:13
0

It can be done by providing template to your directive like below.

app.directive('compile', function($compile) {
return{
    restrict: 'A',
    template: '<p>List of products from {{supplier.name}}</p>
               <p ng-repeat="ref in refs">{{ref}}</p>'
    scope: {
     refs: '='
     supplier: '='
    },
    link: function(scope, element, attrs) {
       # Your code goes here 
    }
}

})

Monika
  • 81
  • 7