2017-06-12 16 views
10

私は、HTMLテンプレートといくつかの情報から電子メールテンプレートを作成できる関数を書いています。このため私は$compileの角度関数を使用しています。

解決できない問題は1つだけです。テンプレートは、ng-includeの無制限量の基本テンプレートで構成されています。私が「ベストプラクティス」を使用するとき$timeoutadvised hereng-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; 
    }); 

私は、この機能はまだ完全に(workarroundが$timeout関数をネストされた)にコンパイルされていないテンプレートを返すために開始したテンプレートにng-include年代を追加するために開始します。私はこれがng-includeの非同期の性質のためだと信じています。


コード

このコードは、それが(関数が再利用できるようになりました、see this question for the problem)レンダリング行われている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答えを試した後、私は最終的に$のコンパイルが行われたときのチェックの他の方法を見つけました。これにより、以下のコードが生成されました。 $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; 
} 

答えて

3

$compileのようなものが同期関数であるだろう。与えられたDOMを同期的にコンパイルするだけで、ネストされたディレクティブで何が起こっているかは気にしません。ネストされたディレクティブが非同期的にテンプレートを読み込んだ場合や、同じティックでコンテンツが使用できないようにするものがある場合、これは親ディレクティブの問題ではありません。

データバインディングと角度コンパイラの作業によって、いつでもどこでも変更が発生する可能性があるため、DOMを確実に「完全」とみなすことができます。 ng-includeにはバインディングも含まれ、含まれるテンプレートはいつでも変更して読み込むことができます。

ここでの実際の問題は、これが後でどのように管理されるかを考慮しなかったという決定です。ランダムテンプレートのng-includeはプロトタイプ作成には問題ありませんが、設計上の問題が発生します。これもその1つです。

この状況を処理する1つの方法は、どのテンプレートが関係しているかを確実にすることです。うまく設計されたアプリケーションは、その部分で余りにも緩い余裕がありません。実際の解決方法は、このテンプレートの元の場所とランダムなネストされたテンプレートが含まれる理由によって異なります。しかし、使用されるテンプレートは、テンプレートがキャッシュされてから使用されるべきであるという考え方です。これは、gulp-angular-templatesのようなビルドツールで行うことができます。またはng-includeコンパイルの前に要求を行うことによって$templateRequest(本質的に$httpを要求して$templateCacheに出す)を行う - $templateRequestは基本的にはng-includeです。

テンプレートがキャッシュされたときに$compile$templateRequestが同期しているものの、ng-includeではない - それは完全にすなわち$timeoutゼロ遅延(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()); 
    }) 
}); 

は、一般的に使用されたテンプレートを置きますキャッシュの作成は、ng-includeだけでなく、ディレクティブのために、角テンプレートがコンパイルのライフサイクルにもたらす非同期性を取り除くための好ましい方法です。

もう1つの方法はng-include eventsです。この方法では、アプリケーションはより緩やかでイベントベースになります(時には良いことですが、ほとんどの場合はそうではありません)。各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()); 
    } 
    }); 
}) 

お知らせ非同期性は、非同期テンプレート要求によって引き起こされます。

+0

ありがとうございました。しかし、私は2番目のソリューションを私の機能に統合する方法を見つけることができないようです(私のトピックの質問を参照)。問題は、作成したスコープオブジェクトでイベントウォッチを設定したときに、イベントがトリガされないことです。これを私の機能にどのように組み込むべきかの例がありますか? ohとplunkrは機能しません。それは私にどんなhtml出力も与えません。 –

+0

パンクが働いています。それは 'console.log'ステートメントを持っています。コンソールを確認してください。私はあなたが統合について何を意味するか分かりません。スコープでウォッチャーを設定し、$ compileを呼び出す必要があります。順序はここでは問題ではありませんが、ウォッチャーを最初に設定してください。これがあなたにとってうまくいかない場合、問題を再現できる塊を提供することを検討してください。いずれにせよ、ng-includeは1.0以降の従来のディレクティブであり、現在のAngularのベストプラクティスに準拠していないため、可能であれば避けるべきです。 – estus

+0

私は、$ rootScope。$ new()を使用しているため、サービスにスコープはありません。イベントは発生しません。あなたはなぜ、そして、もし$ rootScopeがそれを引き起こしているのか知っていますか?あなたはどんな解決法を知っていますか? http://plnkr.co/edit/ZEVSG7TBpYirR77UDxcF?p=preview –

1

私はあなたが約束を守り、イベントをコンパイルすることに悩まされると思います。私はあなたの質問のシリアルと、おそらくあなたが探しているもの、つまり再帰的な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は私が必要とする出力テンプレートのすべてを包むためのコンテナ要素ですので、あなたが好きなものを、これまでの要素にそれを変更することができます。 Angularの$ qを使用することによって、非同期に動作するため、出力テンプレートをキャプチャするための約束関数を公開します。

$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

、あなたは活字体2.1を使用している場合は、コードではなく、コールバックを使用して、よりきれいに見えるようにするためにasync/awaitを使用することができます。それは

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

あなたは$ compile関数が非同期だが、何らかの "done"コールバックを実装していないことを暗示していますか? –

+1

@EricMORAND $ compileは非同期関数で、終了時に通知できるフックはありません。テンプレート内の要素もasync(例:ng-include)であり、フックもありません。この$コンパイルのために、コンパイルが完了した時点を教えてくれません。 $ timesoutは、ブラウザスタックの最後にイベントを追加するため、推奨されています。ほとんどの場合、$ timeoutが実行されると$コンパイルが行われます。残念なことに、これは、非同期であり、ブラウザスタックの最後にイベントを作成するため、これを廃止します。 –

+0

@Telvin Nguyen、 ありがとうございます。しかし、テンプレートに何がインポートされているのかわからないので(この数はどれくらいあるのでしょうか)、この例は私にとってはうまくいきません。このため私は自分の機能を伝えるIDをどこに置くべきかを判断することができません。また、それはjQueryを使用しています。このプロジェクトでは私がアクセスできないライブラリです。 –