5

CONTEXT

I need to load in my AngularJS (v1.4) app some HTML gotten from backend and insert it (the html) into my partial (already loaded). The partial has already some HTML loaded (and completely functional). Right now I'm able to load the HTML and compile it with a directive posted here (Compiling dynamic HTML strings from database). See code below.

PROBLEM

But...when part of the HTML is already loaded (partial loaded and functional) and then I get another HTML content from backend, and the directive is compiling that new one, the entire document (DOM) gets "freezed". I can't type on inputs or do any click on buttons, including those in my previous loaded HTML.

QUESTION

How could I load HTML content, $compile it in "background" or any other way that allows me to continue using the rest of the (already functional) HTML?

It is for me a requisite that the new html content that arrives gets compiled because it contains angular validations and so on that need to be compiled and get inside the "angular world" (be inside the angular digest cycle and so on).

This is the directive I'm using for compiling the html

(function () {

    var dynamic = function($compile) {
        return {
            restrict: 'A',
            replace: true,
            link: function (scope, ele, attrs) {
                scope.$watch(attrs.dynamic, function(html) {
                    if (html) {
                        ele.html(html);
                        $compile(ele.contents())(scope);
                    }

                });
            }
        };
    };

    dynamic.$inject = ['$compile'];

    angular.module('app')
        .directive('dynamic', dynamic);
}());

In the controller I've something like

// this will be filled with asynchronous calls were I get the HTMLs from a service
// in order to keep this example simple I just made a demo, not with the real async calls
$scope.secciones = []

//when the promises are getting resolved "secciones" would be something like (more items can be added after in time)
$scope.secciones = [
    {html: "<div> some html content here (not too small sometimes) </div>"},
    {html: "<div> another html content here (not too small sometimes) </div>"}
]

...and in the view

<!--every time an async call with html is resolved, it's added to secciones, then a new div is generated and compiled-->
<!-- if there was some html previously rendered and the app starts compiling new html the UI gets "freezed"-->
<div ng-repeat="item in secciones">
    <div dynamic="item.html"></div>
</div>

Note: I'm using this approach because each html represents a tab in a tabpanel I have, in which the user actually sees only one html of all of them in "secciones" (the others are hidden, but still there), but I need to compile the others in order to get them ready for the user when he/she click that other tab (another html in secciones).

If there could be any solution to this by upgrading to a newer version of AngularJS(1.x), let's say 1.6, for instance. I'd would be glad to try it out.

lealceldeiro
  • 14,342
  • 6
  • 49
  • 80
  • Use `$timeout` to wrap the compiling of the template. This way you wont block the main thread and your app will be responsive – Kliment Nov 30 '16 at 15:24
  • There's also $scope.$evalAsync. Ref: https://docs.angularjs.org/api/ng/type/$rootScope.Scope – Dominic Aquilina Nov 30 '16 at 15:39
  • Thanks guys. Using the directive I mentioned in my question I did ` scope.$evalAsync( function() { ele.html(html); $compile(ele.contents())(scope); } ); ` and ` $timeout(function () { ele.html(html); $compile(ele.contents())(scope); }) ` The behavior was the same :( – lealceldeiro Nov 30 '16 at 18:29
  • @AsielLealCeldeiro maybe you can add the code of your directive to the question i order to find the right solution – Kliment Dec 01 '16 at 10:34
  • @Kliment I added the code of the directive I mentioned initially in the question and an example of what I'm doing. Hope it helps to clarify the situation. Any other question, please, feel free to ask. Thank you very much for your help! – lealceldeiro Dec 01 '16 at 14:53

2 Answers2

2

Basically I have done this by getting html from script tag and compile it and append to existing div. You can use following snippet.

Include div in your html

<div id="step-container">

</div>

Controller code

var template = $templateCache.get('basicInfo'); // or your html
$compile($("#step-container").html(template).contents())($scope);
$("#step-container").show();

For demonstration this will be included in html page

<script type="text/ng-template" id="basicInfo"></script>

In this way you can compile you html coming from backend.

Hope it helps.

Ghazanfar Khan
  • 3,648
  • 8
  • 44
  • 89
  • As soon as I give it a try I'll give you some feedback. Thank U very much for your help! – lealceldeiro Nov 28 '16 at 01:13
  • I've been playing around with your solution, but I think I misunderstood what the $templateCache.get method does. I though it would receive some html content (including a string html), but it seem that only htmll content, in a file, for instance it's possible to be passed to that method. I'm passing a string represtation of the html I want to render but the method $templateCache.get returns undefined, Please see this screenshot of my devtools. "data" is the html string and template is the return of the $templateCache.get Please see https://www.dropbox.com/s/5k2m3gs56grxoqp/sample.png?dl=0 – lealceldeiro Nov 28 '16 at 18:49
  • You dont have to use template cache just pass your html as string . I have commented // or your html // there , that was just for example var template = "
    " OR When you get your html from backend add this to template cache there and then receive from it
    – Ghazanfar Khan Nov 28 '16 at 20:00
  • thank you so much!, but I've been unable to get this working without affecting the usability of the page. It's a key point on my question that when some (new)html is being compiled by angular, it doesn't block the rest of the page, and it can be "added" to the DOM content "smoothly". I've used another variant, according to https://docs.angularjs.org/api/ng/service/$templateCache and tried to do a `$templateCache.put("myId" ,data);` where *data* is the new html that I want to render and in the html I put `
    ` but no luck, tihs behaves the same way
    – lealceldeiro Nov 28 '16 at 21:17
  • The solution actually compiles my html and gets it into the page, but the process still freezes the webpage. Is the `$("#step-container").show();` intended to "integrate" the new content only when it is ready? Maybe I'm using it incorrectly. Thanks for your help again! – lealceldeiro Nov 28 '16 at 21:19
  • Yes. My first approach was just compile the html and putting where I needed. I used the directive posted here: http://stackoverflow.com/questions/18157305/compiling-dynamic-html-strings-from-database – lealceldeiro Nov 28 '16 at 21:36
  • Have you called $scope.$apply() after that ? – Ghazanfar Khan Nov 28 '16 at 21:37
  • I don't need to. When the html is compiled and ready, it's shown on the page. My problem is when it takes some seconds to compile, my page (and finally the user) needs to wait until the other (new) html is being compiled. Meanwhile the user could use some other parts of the html already rendered (and not wait until all portions of the page is loaded): that is my problem :( – lealceldeiro Nov 28 '16 at 21:43
  • Either you show the full screen loader or use ng-cloak on main container to render html when it is ready , that's is the only solution – Ghazanfar Khan Nov 28 '16 at 21:45
  • Yeh. I was afraid that is the only solution so far :( I'm doing it right now. AngularJS, JavaScript, web browser developers or whoever is at top of web development should implement some `asyncCompile` that would rock! Thanks for your help! – lealceldeiro Nov 28 '16 at 21:48
  • @AsielLealCeldeiro, angular $compile takes time if the scope properties used in the partials are huge, for example , if you're iterating over an array to create a list.what kind of elements are you trying render? if you are rendering a table or list of sort, try looking up some techniques by which you can render a table without overloading your browser. ```virtual-scrolling``` is technique to render huge table or list without overloading browser – balajisoundar Nov 30 '16 at 10:11
  • @Ghazanfar, Loader wont be animated, on this case , since entire UI is forzen – balajisoundar Nov 30 '16 at 10:15
  • @balajisoundar I'm trying to render some html contents gotten from a backend service. I do some requests, each one of them gives me an html content (some of them are not small, some are huge, they contain angular validations, etc.) and then I $compile each one of them and put it into my partial. Some arrive later than others, so I want to show those already compiled and be able to keep rendering the new one without blocking the screen. – lealceldeiro Nov 30 '16 at 18:24
1

You can try use this directive to compile the HTML code. This directive compile the code when to detect any change in variable htmlCode

module.directive('bindHtmlCompile', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                scope.$watch(function () {
                    return scope.$eval(attrs.bindHtmlCompile);
                }, function (value) {
                    element.html(value && value.toString());
                    var compileScope = scope;
                    if (attrs.bindHtmlScope) {
                        compileScope = scope.$eval(attrs.bindHtmlScope);
                    }
                    $compile(element.contents())(compileScope);
                });
            }
        };
    }]);

you can install via bower, DirectiveRepo

USAGE:

<div bind-html-compile="htmlCode"></div>

Isma90
  • 661
  • 2
  • 14
  • 30
  • Thank you @Isma90 for your help. I just tried with your directive, but the final result is the same. When the html is kind of huge, the screen freezes as before. – lealceldeiro Dec 01 '16 at 01:09