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

Mr.*_*guy 11 javascript asynchronous angularjs typescript

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

我似乎无法解决一个问题.该模板由一个基本模板组成,其中包含无限量的模板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;
     });
Run Code Online (Sandbox Code Playgroud)

当我开始向ng-include模板添加's时,此函数开始返回尚未完全编译的模板(workarround是嵌套$timeout函数).我相信这是因为a的异步性质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;
     });
Run Code Online (Sandbox Code Playgroud)

我想要的是

我希望有一个功能可以处理无限量的,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;
}
Run Code Online (Sandbox Code Playgroud)

Est*_*ask 3

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

由于数据绑定和 Angular 编译器的工作方式,没有什么明显的时刻可以认为 DOM 确实是“完整的”,因为变化可能在任何地方、任何时间发生。ng-include也可能涉及绑定,并且包含的​​模板可以随时更改和加载。

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

处理这种情况的一种方法是增加涉及哪些模板的确定性;设计良好的应用程序的各个部分不能过于松散。实际的解决方案取决于该模板的来源以及它为何包含随机嵌套模板。但我们的想法是,使用过的模板应该在使用之前放入模板缓存中。这可以使用诸如gulp-angular-templates. ng-include或者通过在编译之前执行请求$templateRequest(本质上是执行$http请求并将其放入$templateCache) - 执行$templateRequest基本上就是执行ng-include的操作。

虽然当模板被缓存时$compile和是同步的,但不是 - 它在下一个tick上完全编译,即零延迟(一个plunk):$templateRequestng-include$timeout

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());
  })
});
Run Code Online (Sandbox Code Playgroud)

一般来说,将模板放入缓存是摆脱 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());
    }
  });
})
Run Code Online (Sandbox Code Playgroud)

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