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

2024-04-06

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

只有一个问题我似乎无法解决。该模板由一个基本模板和无限数量的ng-include的。当我使用“最佳实践”时$timeout (在这里建议 https://stackoverflow.com/questions/17505484/why-is-the-content-of-an-ng-include-dynamically-loaded-template-not-available-vi)当我删除所有的ng-include的。所以这不是我想要的。

$超时示例:

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到模板,此函数开始返回尚未完全编译的模板(解决方法是嵌套$timeout功能)。我相信这是因为 a 的异步性质ng-include.


工作代码

此代码在渲染完成后返回 html 模板(函数现在可以重用,看到这个问题的问题 https://stackoverflow.com/questions/44541084/recursive-function-in-promise-with-timeout-resolves-to-quick)。但这个解决方案是一个很大的失败,因为它使用的是角度私有$$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并仅在模板创建成功时返回。我不渲染此模板,需要返回完全编译的模板。


Solution

在尝试了 @estus 答案之后,我终于找到了另一种检查 $compile 何时完成的方法。这导致了下面的代码。我使用的原因$q.defer()这是因为模板在事件中被解析。因此,我无法像正常承诺一样返回结果(我不能做return scope.$on())。这段代码中唯一的问题是它严重依赖于ng-include。如果您提供的函数模板没有ng-include the $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 is 同步功能。它只是同步编译给定的 DOM,并不关心嵌套指令中发生了什么。如果嵌套指令具有异步加载的模板或其他阻止其内容在同一时间点上可用的内容,则这不是父指令所关心的问题。

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

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

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

虽然$compile and $templateRequest缓存模板时是同步的,ng-include不是 - 它会在下一个时钟周期完全编译,即$timeout零延迟(aplunk http://plnkr.co/edit/u8Z9GLq9xQybiGJdIbtw?p=info):

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 events https://docs.angularjs.org/api/ng/directive/ngInclude#includeContentRequested。这样,应用程序变得更加松散并且基于事件(有时这是一件好事,但大多数时候不是)。由于每个ng-include发出一个事件,需要对事件进行计数,当它们计数时,这意味着层次结构ng-include指令已完全编译(aplunk http://plnkr.co/edit/ZycEiP78IeU4fkAwTKlC?p=info):

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());
    }
  });
})

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何检查$compile是否已经完成? 的相关文章

随机推荐