4

In my Angular 1.5.11 app, I'm trying to programmatically compile a template and get the result as a HTML string. The content of the template is

<div ng-if="foo">
  <div>foo is {{foo}}, bar is {{bar}}</div>
</div> 

My attempt at compiling it to a HTML string:

app.controller('MainCtrl', function($scope, $rootScope, $compile) {

  function compileHtmlTemplate (templateContent, model) {
    var templateScope = $rootScope.$new(true);
    angular.extend(templateScope, model);

    return $compile(templateContent)(templateScope);
  }

  var templateContent = $('#template').html();
  var model = {foo: 'fooValue', bar: 'barValue'};
  var compiledHtml = compileHtmlTemplate(templateContent, model);
  var htmlAsString = $('<div></div>').html(compiledHtml).html();

  $scope.result = htmlAsString;
});

However, as you can see in this Plunker example, it doesn't work. I need to compile the template rather than just interpolating it, because it contains an ng-if directive.

18
  • Trying to invent wheel? Or what you try to get? As a commented in your previous question you probably want $interpolate not $compile. Commented Apr 7, 2017 at 11:19
  • you need to $eval the values after compiling Commented Apr 7, 2017 at 11:22
  • @PetrAveryanov $interpolate won't work because the template contains directives Commented Apr 7, 2017 at 11:37
  • 1
    @blackmiaool foo has a value so ng-ifevaluates as true. Commented Apr 12, 2017 at 3:18
  • 1
    @estus The OP didn't say it wasn't a useful, valid question; just that they don't want to explain it further. That's entirely their prerogative. Details could be commercially sensitive, for one. Also, at 100k they have been around long enough to have some level of experience under the hood. Commented Apr 16, 2017 at 5:13

4 Answers 4

2
+100

Angular's digest cycle needs to complete before you'll have access to the value. You can achieve that by using $timeout.

I've used $templateCache to retrieve the template since that's the official way, but that's just a personal preference.

var app = angular.module('plunker', ['ngSanitize']);

app.controller('MainCtrl', function($scope, $rootScope, $compile, $timeout, $templateCache) {

  function compileHtmlTemplate (templateContent, model) {
    var templateScope = $rootScope.$new(true);
    angular.extend(templateScope, model);

    var compiled = $compile(templateContent)(templateScope);

    var parent = $("<div>").html(compiled);

    console.log("Won't work! Digest hasn't finished:", parent[0].innerHTML);

    $timeout(function(){ // must wait till Angular has finished digest
      console.log('Will work, digest has now completed:', parent[0].innerHTML);

      $scope.result = parent[0].innerHTML;
    }, 0);
  }

  // either do something with the compiled value within the $timeout, or watch for it
  $scope.$watch('result', function(newValue, oldValue) {
      if (newValue !== undefined) {
        console.log("Do something in the $timeout or here... " + newValue);
      }
  });

  // you can access the template via the template cache if you wanted
  var templateContent = $templateCache.get('template');
  var model = {foo: 'fooValue', bar: 'barValue'};
  var compiledHtml = compileHtmlTemplate(templateContent, model);
});

Updated Plunker: https://plnkr.co/edit/ajC3Iqkxpi6O9gfieHeR?p=preview

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

1 Comment

Updated Plunk to compare results with the rendered the version of the template. Also added commented out test case with foo: false
0

Yes you can use $interpolate like this here

var app = angular.module('plunker', ['ngSanitize']);

app.controller('MainCtrl', function($scope, $rootScope, $compile, $interpolate) {

  function compileHtmlTemplate (templateContent, model) {
    var templateScope = $rootScope.$new(true);
    angular.extend(templateScope, model);

    var tpl = $compile(templateContent)(templateScope);
    console.log(tpl);

    return $interpolate(tpl.html(),model)
  }

  var templateContent = $('#template').html();

  var model = {foo: 'fooValue', bar: 'barValue'};
  var compiledHtml =compileHtmlTemplate(templateContent)(model);
  //console.log(compiledHtml)
  var htmlAsString = $('<div></div>').html(compiledHtml).html();

  $scope.result = htmlAsString;
});

Returning the compiled html with model interpolation then using same.

$compile returns an element not an html string

https://plnkr.co/edit/TPvOXb5TWHGUunBMFubF?p=preview

1 Comment

Thanks for your answer. I forgot to mention that the template has a directive in it, so I need to compile it. I tried your solution after adding an ng-if directive to the template, but it doesn't work. I've updated my question and the Plunker example to include a directive in the template.
0

If you want to display an HTML you can't simply use {{}} you would need to use ng-bind-html or utilise the $sanitaze service of angular

I've updated your plunker https://plnkr.co/edit/GwM5CEemo961s2CeUsdA?p=preview

var compiledHtml = compileHtmlTemplate(templateContent, model);
var element = angular.element('<div></div>').html(compiledHtml)
$timeout(function() {
  $scope.result = element.html()
})

Because of the ngIf in template the compiledHtml returned only ngIf comment, I've fought it before and the only solution I came up with is as above when you append the compiledHtml to newly created element and retrieving the html in $timeout without time specified as it will happen after the appending and $digest cycle. Hope that helps

Comments

-1

below is the working version of your code (most of change are related to do things in angular way)

  var templateContent = $templateCache.get("template");
  //var templateContent = $('#template').html();
  //comment: get html in angular way, not using jquery

  var model = {foo: 'fooValue', bar: 'barValue'};
  var templateScope = $rootScope.$new(true);
  templateScope = angular.extend(templateScope, model);
  //angular.extend(templateScope, model);
  //comment: make sure to capture result of angular.extend

  var compiledElement = $compile(templateContent)(templateScope);
  //var compiledHtml = $compile(templateContent)(templateScope);
  //comment: result of compilation is not an html but an angular element

  var targetElement = angular.element("#result");
  //var htmlAsString = $('<div></div>').html(compiledHtml).html();
  //comment: find the element using angular and not jquery


  targetElement.append(compiledElement);
  //$scope.result = htmlAsString;
  //comment: don't just assign string, append an angular element

update plunker link

a small change in html is

  replaced
  <div>{{result}}</div>

  with
  <div id="result"></div>

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.