繁体   English   中英

如何检查$ compile是否已完成?

[英]How to check if $compile has been completed?

我正在编写一个可以从HTML模板创建电子邮件模板的函数以及给出的一些信息。 为此,我使用Angular的$compile函数。

我似乎无法解决一个问题。 该模板由一个基本模板和无限量的ng-include 当我使用'最佳实践' $timeout这里建议 )当我删除所有ng-include时,它可以工作。 所以这不是我想要的。

$ timeout示例:

return this.$http.get(templatePath)
    .then((response) => {
       let template = response.data;
       let scope = this.$rootScope.$new();
       angular.extend(scope, processScope);

       let generatedTemplate = this.$compile(jQuery(template))(scope);
       return this.$timeout(() => {
           return generatedTemplate[0].innerHTML;
       });
    })
    .catch((exception) => {
        this.logger.error(
           TemplateParser.getOnderdeel(process),
           "Email template creation",
           (<Error>exception).message
        );
        return null;
     });

当我开始将ng-include添加到模板时,此函数开始返回尚未完全编译的模板(workarround嵌套$timeout函数)。 我相信这是因为ng-include的异步性质。


工作代码

此代码在完成渲染时返回html模板(现在可以重用函数, 请查看此问题的问题 )。 但是这个解决方案是一个很大的问题,因为它使用有角度的私有$$phase来检查是否有任何正在进行的$digest 所以我想知道是否有其他解决方案?

return this.$http.get(templatePath)
   .then((response) => {
       let template = response.data;
       let scope = this.$rootScope.$new();
       angular.extend(scope, processScope);

       let generatedTemplate = this.$compile(jQuery(template))(scope);
       let waitForRenderAndPrint = () => {
           if (scope.$$phase || this.$http.pendingRequests.length) {
               return this.$timeout(waitForRenderAndPrint);
           } else {
               return generatedTemplate[0].innerHTML;
           }
        };
        return waitForRenderAndPrint();
    })
    .catch((exception) => {
        this.logger.error(
           TemplateParser.getOnderdeel(process),
           "Email template creation",
           (<Error>exception).message
         );
         return null;
     });

我想要的是

我希望有一个功能可以处理无限量的ng-inlude ,并且只有在模板成功创建后才会返回。 我不渲染此模板,需要返回完全编译的模板。


在尝试了@estus回答后,我终于找到了另一种检查$ compile完成的方法。 这导致了下面的代码。 我使用$q.defer()的原因是因为模板在事件中被解析。 由于这个原因,我不能像正常的承诺那样返回结果(我不能return scope.$on() )。 此代码中唯一的问题是它严重依赖于ng-include 如果您为该函数提供了一个没有ng-include的模板,那么$q.defer永远不会被重新调用。

/**
 * Using the $compile function, this function generates a full HTML page based on the given process and template
 * It does this by binding the given process to the template $scope and uses $compile to generate a HTML page
 * @param {Process} process - The data that can bind to the template
 * @param {string} templatePath - The location of the template that should be used
 * @param {boolean} [useCtrlCall=true] - Whether or not the process should be a sub part of a $ctrl object. If the template is used
 * for more then only an email template this could be the case (EXAMPLE: $ctrl.<process name>.timestamp)
 * @return {IPromise<string>} A full HTML page
*/
public parseHTMLTemplate(process: Process, templatePath: string, useCtrlCall = true): ng.IPromise<string> {
   let scope = this.$rootScope.$new(); //Do NOT use angular.extend. This breaks the events

   if (useCtrlCall) {
       const controller = "$ctrl"; //Create scope object | Most templates are called with $ctrl.<process name>
       scope[controller] = {};
       scope[controller][process.__className.toLowerCase()] = process;
    } else {
       scope[process.__className.toLowerCase()] = process;
    }

    let defer = this.$q.defer(); //use defer since events cannot be returned as promises
    this.$http.get(templatePath)
       .then((response) => {
          let template = response.data;
          let includeCounts = {};
          let generatedTemplate = this.$compile(jQuery(template))(scope); //Compile the template

           scope.$on('$includeContentRequested', (e, currentTemplateUrl) => {
                        includeCounts[currentTemplateUrl] = includeCounts[currentTemplateUrl] || 0;
                        includeCounts[currentTemplateUrl]++; //On request add "template is loading" indicator
                    });
           scope.$on('$includeContentLoaded', (e, currentTemplateUrl) => {
                        includeCounts[currentTemplateUrl]--; //On load remove the "template is loading" indicator

            //Wait for the Angular bindings to be resolved
            this.$timeout(() => {
               let totalCount = Object.keys(includeCounts) //Count the number of templates that are still loading/requested
                   .map(templateUrl => includeCounts[templateUrl])
                   .reduce((counts, count) => counts + count);

                if (!totalCount) { //If no requests are left the template compiling is done.
                    defer.resolve(generatedTemplate.html());
                 }
              });
          });
       })
       .catch((exception) => {                
          defer.reject(exception);
       });

   return defer.promise;
}

$compile同步函数。 它只是同步编译给定的DOM,而不关心嵌套指令中发生了什么。 如果嵌套指令具有异步加载的模板或其他阻止其内容在同一刻度上可用的内容,则这不是父指令的关注点。

由于数据绑定和Angular编译器的工作方式,没有明显的时刻可以将DOM视为“完整”,因为任何时候都可能在每个地方都发生变化。 ng-include也可能涉及绑定,并且可以随时更改和加载包含的模板。

这里的实际问题是没有考虑以后如何管理这个问题的决定。 ng-include随机模板可以用于原型设计,但会导致设计问题,这就是其中之一。

处理这种情况的一种方法是增加一些涉及哪些模板的确定性; 精心设计的应用程序不能在其零件上过于松散。 实际的解决方案取决于此模板的来源以及包含随机嵌套模板的原因。 但是我们的想法是,在使用模板之前,应该将使用过的模板放到缓存模板中。 这可以通过gulp-angular-templates等构建工具完成。 或者通过使用$templateRequest (基本上执行$http请求并将其放入$templateCache )进行ng-include编译之前的请求 - 执行$templateRequest基本上是ng-include作用。

虽然模板缓存时$compile$templateRequest是同步的,但ng-include不是 - 它会在下一个tick上完全编译,即$timeout with zero delay( 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());
  })
});

通常将模板用于缓存是消除Angular模板为编译生命周期带来的异步性的首选方法 - 不仅适用于ng-include还适用于任何指令。

另一种方法是使用ng-include事件 通过这种方式,应用程序变得更加松散且基于事件(有时它是一件好事,但大多数情况下并非如此)。 由于每个ng-include发出一个事件,因此需要对事件进行计数,当这些事件发生时,这意味着ng-include指令的层次结构已经完全编译(一个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());
    }
  });
})

请注意,这两个选项仅处理异步模板请求引起的异步性。

我认为你会被承诺和编译事件束缚住。 我按照你的问题的序列,这可能是你正在寻找的,带有递归ng-include的编译模板字符串。

首先,我们需要定义自己的函数以检测编译何时完成,有几种方法可以实现,但持续时间检查是我最好的选择。

// 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)


  }

在下面的示例中, directive-one是包含我需要的所有输出模板的容器元素,因此您可以将其更改为您喜欢的任何元素。 通过使用$ q的Angular,我将公开promise函数来捕获输出模板,因为它工作异步。

$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; 我的Demo plunker

另外,如果您使用的是TypeScript 2.1,则可以使用async / await使代码看起来更清晰,而不是使用回调。 它会是这样的

var myOutput = await $scope.getOutput('template-path')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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