5

I am using ng-bind-html for binding data that I get from database.

<p ng-bind-html="myHTML"></p>   


app.controller('customersCtrl', function($scope, $http, $stateParams) {
    console.log($stateParams.id);
    $http.get("api link"+$stateParams.id)
    .then(function(response) {
      $scope.myHTML = response.data.content;

        // this will highlight the code syntax
        $('pre code').each(function(i, block) {
            hljs.highlightBlock(block);
        });
    });
});

When the data displayed on the screen, I want to run

$('pre code').each(function(i, block) {
      hljs.highlightBlock(block);
});

for highlight the code syntax in the data but it is not highlight. (I use highlight library in CKEditor for highlight the code syntax)

And if I delay load the highlight code after 1s, it will work but I think it is not a good solution

setTimeout(function () {
    $('pre code').each(function(i, block) {
        hljs.highlightBlock(block);
    });
  }, 1000);

I think maybe the highlight code run before ng-bind-html finished.

=== UPDATE
I am using $timeout with delay time 0 as some person recommend. However, sometime when the network is slow and the page load slow, the code will not highlighted .

$scope.myHTML = response.data.content;
$timeout(function() {
  $('pre code').each(function(i, block) {
      hljs.highlightBlock(block);
  });
}, 0);
Linh
  • 57,942
  • 23
  • 262
  • 279
  • use `$timeout` instead of `setTimeout`; `$timeout` is a wrapper that ensures that a `$digest` is processed. you may not even need to have a 1sec delay in that instance, just the amount of time it takes for the `$digest` to process. – Claies Dec 11 '15 at 02:32
  • I want to know what is amount of time in this instance take? how to calculate it? – Linh Dec 11 '15 at 02:36
  • you don't need to calculate it; `$timeout` automatically issues a `$digest` (which redraws the UI) and then performs the logic inside, instead of waiting for a static amount of time. – Claies Dec 11 '15 at 02:47
  • Thank you. I will start learn about `$timeout` and `$digest` for do it. – Linh Dec 11 '15 at 02:53

3 Answers3

6

This is where directives come in very handy. Why not append the HTML yourself and then run the highlighter?

Template:

<div ng-model="myHTML" highlight></div>

Directive:

.directive('highlight', [
    function () {
        return {
            replace: false,
            scope: {
                'ngModel': '='
            },
            link: function (scope, element) {
                element.html(scope.ngModel);
                var items = element[0].querySelectorAll('code,pre');
                angular.forEach(items, function (item) {
                    hljs.highlightBlock(item);
                });

            }
        };
    }
]);

Example: http://plnkr.co/edit/ZbcNgfl6xL2QDDqL9cKc?p=preview

iH8
  • 27,722
  • 4
  • 67
  • 76
  • 2
    This seems to be more professional way to implement this feature (than timeouts). Basically, this kind of logic should be placed inside of directive. There is one note about this: you should consider renaming `ngModel` to something different, because current implementation suggests that it requires `ngModel` and gives you additional features of `ngModel` (which is not true). – rzelek Jan 02 '16 at 22:23
  • @kabrice, unsure what problem you're experiencing but if you look at the example i supplied, it works perfectly. – iH8 Feb 06 '17 at 11:35
4

So here's what is happening:

  1. You update the $scope.myHTML value
  2. You run your jQuery each() loop
  3. The digest cycle runs and your template is updated

Notice that the digest cycle runs after your jQuery each() loop -- or, more specifically, after your $http callback function is finished running.

That means the value of $scope.myHTML in your controller is not applied to the ng-bind-html directive until after your loop has already finished.

To overcome this, you could use Angular's $timeout service instead of the native browser setTimeout() method. By default, $timeout will invoke the callback function during the next digest cycle, which means it will run after the changes to $scope.myHTML are applied to the ng-bind-html directive (as long as you update $scope.myHTML before calling $timeout()).

Working example: JSFiddle

Shaun Scovil
  • 3,905
  • 5
  • 39
  • 58
  • sorry for later response. Now, I'm using `$timeout` but sometime (about 1 /10 times I refresh the page) the code will not highlighted. Please see my update post – Linh Jan 04 '16 at 02:06
  • I cannot reproduce, even when the HTTP response takes 10 seconds. See this variation of my JSFiddle, in which you can adjust the delay on the response and it will still run the timeout function afterward: https://jsfiddle.net/sscovil/qaasL9nx/ -- if the highlighting isn't working at times, I am fairly certain it is unrelated to your use of `$timeout`. – Shaun Scovil Jan 04 '16 at 02:25
  • That said, you should not be performing DOM manipulation in a controller so this is not the best method. A custom directive would be more appropriate, as illustrated in iH8 's answer (though you should heed Arek Żelechowski 's comment about naming an attribute `ngModel`). – Shaun Scovil Jan 04 '16 at 02:28
  • Thank you for nice explain, I will try to custom `directive` as iH8 's answer – Linh Jan 04 '16 at 02:29
1

as you know the statements execute asynchronously, if there is no timeout $('pre code') will be empty as the DOM is still not rendered. use $timeout instead of setTimeout for the same.

Monika
  • 31
  • 2
  • Can you please elaborate? – Enamul Hassan Dec 11 '15 at 02:46
  • The statements do not execute asynchronously, they execute in order. The problem is that changing `$scope.myHTML` in the controller doesn't change it in the view until the next digest cycle. But you are correct that `$timeout()` can be used to solve the problem. – Shaun Scovil Jan 01 '16 at 19:17