How to check if $compile has been completed?

后端 未结 2 1968
梦毁少年i
梦毁少年i 2021-02-20 07:24

I am writing a function that can create an email template from a HTML template and some information that is given. For this I am using the $compile function of Angu

相关标签:
2条回答
  • 2021-02-20 07:37

    I think you get stuck by chain the promise and compile event. I followed the serial of your questions and this maybe what you are looking for, the compiled template string with recursive ng-include.

    First, we need to define ourself the function to detect when the compile is completed, there are couple ways to achieve that, but the duration checking is my best bet.

    // pass searchNode, this will search the children node by elementPath, 
    // for every 0.5s, it will do the search again until find the element
    function waitUntilElementLoaded(searchNode, elementPath, callBack){
    
        $timeout(function(){
    
            if(searchNode.find(elementPath).length){
              callBack(elementPath, $(elementPath));
          }else{
            waitUntilElementLoaded(searchNode, elementPath, callBack);
          }
          },500)
    
    
      }
    

    In below example, directive-one is the container element to wrap up all of the output template that I need, so you could change it to what-ever element that you like. By using $q of Angular, I will expose the promise function to capture the output template since it works async.

    $scope.getOutput = function(templatePath){
    
    
      var deferred = $q.defer();
        $http.get(templatePath).then(function(templateResult){
          var templateString = templateResult.data;
          var result = $compile(templateString)($scope) 
    
    
         waitUntilElementLoaded($(result), 'directive-one', function() {
    
           var compiledStr = $(result).find('directive-one').eq(0).html();
            deferred.resolve(compiledStr);
         })
    
        })
    
      return deferred.promise;
    
    
      }
    
    
    
      // usage
    
      $scope.getOutput("template-path.html").then(function(output){
          console.log(output)
        })
    

    TL;DR; My Demo plunker

    In extra, if you are using the TypeScript 2.1, you could use async/await to make the code looks more cleaner instead of using callback. It would be something like

    var myOutput = await $scope.getOutput('template-path')
    
    0 讨论(0)
  • 2021-02-20 07:38

    $compile is synchronous function. It just compiles given DOM synchronously and doesn't care about what's going on in nested directives. If nested directives have asynchronously loaded templates or other things that prevents their content from being available on the same tick, this is not a concern for parent directive.

    Due to how data binding and Angular compiler work, there's no distinct moment when DOM can be considered certainly 'complete', because changes may occur in every place, any time. ng-include may involve bindings too, and included templates may be changed and loaded at any moment.

    The actual problem here is the decision that didn't take into account how this will be managed later. ng-include with random template is ok for prototyping but will lead to design problems, and this is one of them.

    One way to handle this situation is to add some certainty on which templates are involved; well-designed application cannot afford to be too loose on its parts. The actual solution depends on where this template originates from and why it contains random nested templates. But the idea is that used templates should be put to template cached before they will be used. This can be done with build tools like gulp-angular-templates. Or by doing requests prior to ng-include compilation with $templateRequest (which essentially does $http request and puts it to $templateCache) - doing $templateRequest is basically what ng-include does.

    Although $compile and $templateRequest are synchronous when templates are cached, ng-include is not - it becomes fully compiled on the next tick, i.e. $timeout with zero delay (a plunk):

    var templateUrls = ['foo.html', 'bar.html', 'baz.html'];
    
    $q.all(templateUrls.map(templateUrl => $templateRequest(templateUrl)))
    .then(templates => {
      var fooElement = $compile('<div><ng-include src="\'foo.html\'"></ng-include></div>')($scope);
    
      $timeout(() => {
       console.log(fooElement.html());
      })
    });
    

    Generally putting templates in use to cache is the preferable way to get rid of asynchronicity that Angular templates bring to compilation lifecycle - not only for ng-include but for any directives.

    Another way is to use ng-include events. This way the application becomes more loose and event based (sometimes it is a good thing but most times it's not). Since each ng-include emits an event, the events need to be counted, and when they are, this means that a hierarchy of ng-include directives has been completely compiled (a plunk):

    var includeCounts = {};
    
    var fooElement = $compile('<div><ng-include src="\'foo.html\'"></ng-include></div>')($scope);
    
    $scope.$on('$includeContentRequested', (e, currentTemplateUrl) => {
      includeCounts[currentTemplateUrl] = includeCounts[currentTemplateUrl] || 0;
      includeCounts[currentTemplateUrl]++;
    })
    // should be done for $includeContentError as well
    $scope.$on('$includeContentLoaded', (e, currentTemplateUrl) => {
      includeCounts[currentTemplateUrl]--;
    
      // wait for a nested template to begin a request
      $timeout(() => {
        var totalCount = Object.keys(includeCounts)
        .map(templateUrl => includeCounts[templateUrl])
        .reduce((counts, count) => counts + count);
    
        if (!totalCount) {
          console.log(fooElement.html());
        }
      });
    })
    

    Notice that both options will only handle asynchronicity that is caused by asynchronous template requests.

    0 讨论(0)
提交回复
热议问题