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?

6
  • 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 Commented Oct 16, 2013 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? Commented Oct 16, 2013 at 12:06
  • just added some explanation to original post Commented Oct 16, 2013 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. Commented Oct 16, 2013 at 14:05
  • @tennisgent compile is a directive. See 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 Commented Oct 16, 2013 at 14:21

2 Answers 2

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>

Sign up to request clarification or add additional context in comments.

1 Comment

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!
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 
    }
}

})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.