简体   繁体   中英

Angular.js caching $compiled templates / rendering performance of directives inside ng-repeat

I have aa directive which renders table cell(see how the way I'm compiling it here, basically using $compile inside link fn Angular.js directive template using variable from parent/inherited scope ), now this is used inside two ng-repeat s, one for rows, one for columns, so it's basically

<ng-repeat row in rows>
  <ng-repeat column in columns>
    <my-cell-directive />
  </ng-repeat>      
</ng-repeat>

with 50 rows and 8 columns its got a pretty big(well pretty noticeable anyway) impact on (rendering) performance.

So I was looking for a way to improve it. Firstly I tried to get rid of that inner repeat for columns, creating a my-cols-directive which internally iterates over columns, find their template, create one string(with those 8 columns inside) and then compiles it. Which lowered amount of compiling from 400 to 50. But it didn't really have noticeable improvement on rendering(well it did, but only about 15%).

Now my other idea was to reduce it to only one compile somehow, basically compiling it in the first iteration of ng-repeat, then saving(caching) the compiled result so the directive would then use this instead of compiling it over and over again, just replacing binding values with ones from the current iteration.

Would it be possible somehow? Or is there any other way to improve rendering speed?

You should avoid using $compile inside linking function if possible. You can cache parital result of the $compile though.

Use the second argument of the compiled object cloneAttachFn

directive('lol', function($compile){
  var compiled = $compile(template);
  return function(scope, element, attr){
    compiled(scope, function(clonedElement, scope){
      element.append(clonedElement);  
    };
  }
})

example

Expanding on @goofy 's answer and answering @ironic, here is what I came up with:

(function (angular) {

var messageSelectorDirective = ['$compile', function ($compile) {
    // Create cached message templates, this could also come from a service or you can use other caching strategies
    var messageTypeTemplates = {
        'TYPE1': $compile('<message-type1 class="message" />'),
        'IMAGE': $compile('<image-message class="message image-message" maybe-another-directive />'),
        'SMS_FOLLOWUP': $compile('<sms-message class="message message-type3" ng-hide="thisCanWorkToo" />'),
        'DEFAULT': $compile('<message-type1 class="message" />')
    };

    // Based on the supplied message.$type property, select an appropriate directive -- returns a default if not found
    function getCompiledMessageTemplate(message) {
        return angular.isDefined(messageTypeTemplates[message.$type]) ? messageTypeTemplates[message.$type] : messageTypeTemplates['DEFAULT'];
    }

    return {
        restrict: 'A',
        scope: {
            $message: '=message',
            $context: '=context'
            // You could also provide a selector function here that determines how to choose a directive from the message, or it could be a service ...
        },
        link: function (scope, element) {
            var template = getCompiledMessageTemplate(scope.$message);
            var templateElement;

            template(scope, function (clonedElement, scope) {
                templateElement = clonedElement;
                element.append(templateElement);
            });

            template = null;

            element.on("$destroy", function () {
                templateElement.remove();
                templateElement = null;
            });
        }
        // You can optionally have a controller here that allows you operate on the supplied context since this is an isolated directive
        // controller: 'MessageSelectorController'
    };
}];

angular.module('directives').directive('messageSelector', messageSelectorDirective);
})(angular);

Usage in HTML:

...

<ol class="list-unstyled">
    <li class="row" message-selector message="::message" context="::context" ng-repeat="message in filteredMessages = (messages | limitLast:renderLimit) track by message.id">
        <!-- 
        In here will be the properly selected directive rendered according the the message.$type. When you receive the data from the server, you can
        decide how to map an individual message in to a given $type which the directive above will use, OR, you can use another strategy for selection!

        Since you have full control over the template selection, you can also decide what things you want to be in an individual message. You have lots of options here!
        -->
    </li>
</ol>

...

Hopefully this helps someone or gives some ideas!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM