Angular JS: Detect if ng-bind-html finished loading then highlight code syntax
Asked Answered
P

3

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);
Pile answered 11/12, 2015 at 2:10 Comment(4)
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.Derna
I want to know what is amount of time in this instance take? how to calculate it?Pile
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.Derna
Thank you. I will start learn about $timeout and $digest for do it.Pile
D
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

Dayle answered 11/12, 2015 at 3:43 Comment(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).Bodoni
@kabrice, unsure what problem you're experiencing but if you look at the example i supplied, it works perfectly.Dayle
D
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

Diamonddiamondback answered 1/1, 2016 at 19:1 Comment(4)
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 postPile
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: jsfiddle.net/sscovil/qaasL9nx -- if the highlighting isn't working at times, I am fairly certain it is unrelated to your use of $timeout.Diamonddiamondback
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).Diamonddiamondback
Thank you for nice explain, I will try to custom directive as iH8 's answerPile
H
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.

Haeres answered 11/12, 2015 at 2:40 Comment(2)
Can you please elaborate?Rimini
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.Diamonddiamondback

© 2022 - 2024 — McMap. All rights reserved.