2017-06-12 22 views
10

मैं एक ऐसा फ़ंक्शन लिख रहा हूं जो HTML टेम्पलेट से ईमेल टेम्पलेट और कुछ जानकारी दी जा सकती है। इसके लिए मैं $compile कोणीय के कार्य का उपयोग कर रहा हूं।

केवल एक समस्या है जिसे मैं हल नहीं कर सकता। टेम्पलेट में ng-include की असीमित राशि वाला आधार टेम्पलेट होता है। जब मैं 'सर्वोत्तम अभ्यास' $timeout (advised here) का उपयोग करता हूं तो यह काम करता है जब मैं सभी 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; 
    }); 

जब मैं टेम्पलेट इस समारोह टेम्पलेट्स कि अभी तक पूरी तरह संकलित नहीं कर रहे हैं (एक workarround $timeout कार्यों घोंसला बनाने से है) वापस जाने के लिए शुरू होता है के लिए ng-include के जोड़ने के लिए शुरू करते हैं। मेरा मानना ​​है कि यह ng-include की एसिंक प्रकृति की वजह से है।


कार्य कोड

यह कोड लौटाता है जब यह प्रतिपादन किया जाता है (समारोह अब पुन: उपयोग किया जा सकता है, see this question for the problem) एचटीएमएल टेम्पलेट। लेकिन यह समाधान एक बड़ा नहीं है क्योंकि यह $digest के चल रहे हैं या नहीं, यह जांचने के लिए कोणीय निजी $$phase का उपयोग कर रहा है। तो मैं सोच रहा हूं कि कोई अन्य समाधान है या नहीं?

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 कुछ की तरह तुल्यकालिक कार्य है। यह सिर्फ डीओएम को समकालिक रूप से संकलित करता है और नेस्टेड निर्देशों में क्या हो रहा है इसके बारे में परवाह नहीं करता है। यदि नेस्टेड निर्देशों में असीमित रूप से लोड किए गए टेम्पलेट्स या अन्य चीजें हैं जो उनकी सामग्री को उसी टिक पर उपलब्ध होने से रोकती हैं, तो यह माता-पिता निर्देश के लिए चिंता नहीं है।

डेटा बाइंडिंग और कोणीय कंपाइलर कैसे काम करता है, इस बारे में कोई विशिष्ट क्षण नहीं है जब डोम को निश्चित रूप से 'पूर्ण' माना जा सकता है, क्योंकि हर जगह किसी भी समय परिवर्तन हो सकता है। ng-include में बाइंडिंग भी शामिल हो सकती है, और टेम्पलेट्स को किसी भी समय बदला जा सकता है और लोड किया जा सकता है।

वास्तविक समस्या यहां निर्णय है जिसने ध्यान नहीं दिया कि यह बाद में कैसे प्रबंधित किया जाएगा। ng-include यादृच्छिक टेम्पलेट के साथ प्रोटोटाइप के लिए ठीक है लेकिन डिजाइन समस्याओं का कारण बन जाएगा, और यह उनमें से एक है।

इस स्थिति को संभालने का एक तरीका यह है कि कुछ निश्चितताएं शामिल हैं जिन पर टेम्पलेट शामिल हैं; अच्छी तरह से डिज़ाइन किया गया एप्लिकेशन अपने हिस्सों पर बहुत ढीला नहीं हो सकता है। वास्तविक समाधान इस टेम्पलेट को कहां से उत्पन्न करता है और इसमें यादृच्छिक नेस्टेड टेम्पलेट्स क्यों शामिल हैं इस पर निर्भर करता है। लेकिन विचार यह है कि इस्तेमाल किए जाने से पहले इस्तेमाल किए गए टेम्पलेट को कैश किए गए टेम्पलेट पर रखा जाना चाहिए। यह gulp-angular-templates जैसे निर्माण उपकरण के साथ किया जा सकता है। या ng-include$templateRequest (जो अनिवार्य रूप से $http अनुरोध के साथ संकलन करता है और इसे $templateCache पर रखता है) से पहले अनुरोध करके और $templateRequest कर रहा है मूल रूप से ng-include करता है।

हालांकि $compile और $templateRequest तुल्यकालिक जब टेम्पलेट्स कैश नहीं किया जाता है, ng-include नहीं है - यह पूरी तरह से शून्य देरी (एक plunk) के साथ, अगले टिक पर संकलित हो जाता है यानी $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()); 
    }) 
}); 

उपयोग में आम तौर पर डाल टेम्पलेट्स कैश करने के लिए एसिंक्रोनिसिटी से छुटकारा पाने का बेहतर तरीका है कि कोणीय टेम्पलेट जीवन चक्र को संकलित करने के लिए लाते हैं - न केवल ng-include के लिए बल्कि किसी भी निर्देश के लिए।

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

उत्तर के लिए धन्यवाद। फिर भी मुझे अपने फ़ंक्शन में दूसरे समाधान को एकीकृत करने का कोई तरीका नहीं दिख रहा है (मेरा विषय प्रश्न देखें)। समस्या यह है कि जब भी मैं अपने बनाए गए स्कोप ऑब्जेक्ट पर इवेंट घड़ी सेट करता हूं तो ईवेंट किसी भी घटना पर ट्रिगर नहीं होता है। क्या आपके पास एक उदाहरण है कि मुझे इसे अपने कार्य में कैसे एकीकृत करना चाहिए? ओह और आपका प्लंकर काम नहीं करता है। यह मुझे कोई एचटीएमएल आउटपुट नहीं देता है। –

+0

प्लंक काम करता है। इसमें 'console.log' कथन हैं। कंसोल की जांच करें। मुझे यकीन नहीं है कि आप एकीकरण के बारे में क्या मतलब है। आपको स्कोप पर वॉचर्स सेट अप करने और केवल $ संकलन कॉल करने की आवश्यकता है। आदेश यहां कोई फर्क नहीं पड़ता है लेकिन पहले दर्शक को स्थापित करने का प्रयास करें। एक प्लंक प्रदान करने पर विचार करें जो समस्या को फिर से बना सकता है यदि यह आपके लिए काम नहीं करता है। किसी भी तरह, एनजी-शामिल 1.0 के बाद विरासत निर्देश है और यदि संभव हो तो इससे बचा जाना चाहिए क्योंकि यह वर्तमान कोणीय सर्वोत्तम प्रथाओं के अनुरूप नहीं है। – estus

+0

मुझे पता चला कि इस तथ्य के कारण कि मैं $ रूटस्कोप का उपयोग कर रहा हूं। $ नया() (मेरे पास सेवा में कोई गुंजाइश नहीं है) घटनाओं को निकाल दिया नहीं जाता है। क्या आप जानते हैं कि क्यों और यदि $ रूटस्कोप इसका कारण बन रहा है, तो क्या आप कोई समाधान जानते हैं? http://plnkr.co/edit/ZEVSG7TBpYirR77UDxcF?p=preview –

1

मुझे लगता है कि आप वाइन और संकलन घटना श्रृंखला से फंस गए हैं। मैंने आपके प्रश्नों के सीरियल का पालन किया और यह शायद आप जो खोज रहे हैं, संकलित एनजी-शामिल के साथ संकलित टेम्पलेट स्ट्रिंग।

सबसे पहले, हमें संकलन पूरा होने पर पता लगाने के लिए स्वयं को फ़ंक्शन परिभाषित करने की आवश्यकता है, इसे प्राप्त करने के कुछ तरीके हैं, लेकिन अवधि की जांच मेरी सबसे अच्छी शर्त है।तो आप इसे क्या-कभी तत्व यह है कि आप की तरह करने के लिए बदल सकता है

// 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, उत्पादन टेम्पलेट है कि मैं जरूरत के सभी लपेट के लिए कंटेनर तत्व है। कोणीय के $ क्यू का उपयोग करके, मैं आउटपुट टेम्पलेट को कैप्चर करने के लिए वादे फ़ंक्शन का पर्दाफाश करूँगा क्योंकि यह 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) 
    }) 

टीएल; डीआर; My Demo plunker

अतिरिक्त में, यदि आप टाइपप्रति 2.1 का उपयोग कर रहे हैं, तो आप async/await का उपयोग कोड के बजाय कॉलबैक का उपयोग कर के और अधिक क्लीनर लग रहा है बनाने के लिए कर सकता है। यह होगा

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

क्या आप यह कह रहे हैं कि $ संकलन फ़ंक्शन असीमित है लेकिन किसी भी प्रकार के "किए गए" कॉलबैक को लागू नहीं करता है? –

+1

@EricMORAND $ compile एक async फ़ंक्शन है जिसमें कोई हुक नहीं है जो यह करने के बाद आपको बता सकता है। इसे इस तथ्य के साथ करना है कि टेम्पलेट में तत्व एसिंक भी हैं (उदाहरण: एनजी-शामिल) और इसमें कोई भी हुक नहीं है। इस $ संकलन के कारण यह आपको नहीं बता सकता है कि यह कब किया जाता है। $ टाइमआउट को पुनः संयोजित किया गया है, क्योंकि यह ब्राउज़र स्टैक के अंत में एक ईवेंट जोड़ता है। अधिकांश समय $ संकलन निष्पादित होने पर $ संकलन किया जाता है। दुर्भाग्यवश एनजी-शामिल है क्योंकि यह एसिंक भी है और ब्राउज़र स्टैक के अंत में ईवेंट बना रहा है। –

+0

@Telvin Nguyen, आपके उत्तर के लिए धन्यवाद। फिर भी यह उदाहरण मेरे लिए काम नहीं करता है, इस तथ्य के कारण कि मुझे नहीं पता कि टेम्पलेट में क्या आयात किया जाता है (इसमें कितना एनजी शामिल है)। इसके कारण मैं यह निर्धारित नहीं कर सकता कि आईडी कहां रखना है जो मेरे कार्य को बताएगा इसे संकलित किया गया है। यह भी jQuery का उपयोग कर रहा है। एक पुस्तकालय जिसकी मुझे इस परियोजना में उपयोग नहीं है। –

संबंधित मुद्दे