कोणीयजेएस में निर्देश से निर्देश जोड़ें


197

मैं एक निर्देश बनाने की कोशिश कर रहा हूं जो उस पर घोषित किए गए तत्व में अधिक निर्देश जोड़ने का ख्याल रखता है । उदाहरण के लिए, मैं एक निर्देश बनाना चाहता हूं जो जोड़ने का ख्याल रखता है datepicker, datepicker-languageऔर ng-required="true"

यदि मैं उन विशेषताओं को जोड़ने की कोशिश करता हूं और फिर $compileमैं स्पष्ट रूप से एक अनंत लूप उत्पन्न करता हूं, तो मैं जांच कर रहा हूं कि क्या मैंने पहले से ही आवश्यक विशेषताओं को जोड़ दिया है:

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        element.attr('datepicker', 'someValue');
        element.attr('datepicker-language', 'en');
        // some more
        $compile(element)(scope);
      }
    };
  });

बेशक, अगर मैं $compileतत्व नहीं करता हूं, तो विशेषताओं को सेट किया जाएगा, लेकिन निर्देश बूट नहीं किया जाएगा।

क्या यह दृष्टिकोण सही है या मैं इसे गलत कर रहा हूं? क्या समान व्यवहार प्राप्त करने का एक बेहतर तरीका है?

UDPATE : इस तथ्य को देखते हुए कि $compileइसे प्राप्त करने का एकमात्र तरीका है, क्या पहला संकलन पास छोड़ने का एक तरीका है (तत्व में कई बच्चे शामिल हो सकते हैं)? शायद सेटिंग करके terminal:true?

अद्यतन 2 : मैंने निर्देश को एक selectतत्व में डालने की कोशिश की है और, जैसा कि अपेक्षित था, संकलन दो बार चलता है, जिसका मतलब है कि अपेक्षित optionएस की संख्या दोगुनी है ।

जवाबों:


260

ऐसे मामलों में जहां आपके पास एक एकल DOM तत्व पर कई निर्देश हैं और जहां वे जिस मामले में लागू होते हैं उस मामले में, आप priorityउनके आवेदन का आदेश देने के लिए संपत्ति का उपयोग कर सकते हैं । उच्च संख्या पहले भागते हैं। यदि आप एक निर्दिष्ट नहीं करते हैं तो डिफ़ॉल्ट प्राथमिकता 0 है।

EDIT : चर्चा के बाद, यहाँ पूर्ण कार्य समाधान है। कुंजी विशेषता को हटाने के लिए थी : element.removeAttr("common-things");और यह भी element.removeAttr("data-common-things");(यदि data-common-thingsHTML में उपयोगकर्ता निर्दिष्ट करें )

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false, 
      terminal: true, //this setting is important, see explanation below
      priority: 1000, //this setting is important, see explanation below
      compile: function compile(element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {  },
          post: function postLink(scope, iElement, iAttrs, controller) {  
            $compile(iElement)(scope);
          }
        };
      }
    };
  });

वर्किंग प्लंकर पर उपलब्ध है: http://plnkr.co/edit/Q13bUt?p=preview

या:

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false,
      terminal: true,
      priority: 1000,
      link: function link(scope,element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        $compile(element)(scope);
      }
    };
  });

डेमो

स्पष्टीकरण हमें क्यों सेट करना है terminal: trueऔर priority: 1000(एक उच्च संख्या):

जब DOM तैयार हो जाता है, कोणीय सभी पंजीकृत निर्देशों की पहचान करने के लिए DOM चलता है और निर्देशों को एक-एक करके संकलित करता है priority यदि ये निर्देश एक ही तत्व पर हैं । हमने यह सुनिश्चित करने के लिए कि यह पहले संकलित किया जाएगा और यह निर्देश संकलित होने के बाद terminal: trueअन्य निर्देशों को छोड़ दिया जाएगा, यह सुनिश्चित करने के लिए कि हम अपने कस्टम निर्देशन की प्राथमिकता उच्च संख्या पर निर्धारित करते हैं ।

जब हमारा कस्टम निर्देश संकलित किया जाता है, तो यह निर्देशों को जोड़कर और खुद को हटाकर तत्व को संशोधित करेगा और सभी निर्देशों को संकलित करने के लिए $ संकलित सेवा का उपयोग करेगा (जो कि छोड़ दिया गया था)

यदि हम सेट नहीं करते हैं terminal:trueऔर priority: 1000, एक मौका है कि कुछ निर्देश हमारे कस्टम निर्देश से पहले संकलित किए जाते हैं । और जब हमारा कस्टम निर्देश तत्व संकलित करने के लिए $ संकलन का उपयोग करता है => पहले से संकलित निर्देशों को फिर से संकलित करता है। यह अप्रत्याशित व्यवहार का कारण होगा, खासकर अगर हमारे कस्टम निर्देश पहले ही संकलित किए गए निर्देशों को डोम में बदल चुके हैं।

प्राथमिकता और टर्मिनल के बारे में अधिक जानकारी के लिए, निर्देश के `टर्मिनल` को कैसे समझें?

निर्देश का एक उदाहरण जो टेम्पलेट को भी संशोधित करता है ng-repeat(प्राथमिकता = 1000), जब ng-repeatसंकलित किया जाता है, ng-repeat तो अन्य निर्देशों को लागू करने से पहले टेम्पलेट तत्व की प्रतियां बनाएं

@ इज़्हाकी की टिप्पणी के लिए, यहाँ ngRepeatस्रोत कोड का संदर्भ दिया गया है : https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js


5
यह मेरे लिए एक स्टैक अतिप्रवाह अपवाद फेंकता है: RangeError: Maximum call stack size exceededजैसा कि यह हमेशा के लिए संकलित होता है।
frapontillo

3
@frapontillo: आपके मामले में, element.removeAttr("common-datepicker");अनिश्चित लूप से बचने के लिए जोड़ने का प्रयास करें ।
खान

4
ठीक है, मैं इसे सुलझाने के लिए कर लिया है, तो आप सेट करने के लिए है replace: false, terminal: true, priority: 1000; तब compileफ़ंक्शन में वांछित विशेषताएँ सेट करें और हमारे निर्देशन विशेषता को हटा दें। अंत में, postफंक्शन में compileकॉल करके लौटे $compile(element)(scope)। तत्व नियमित रूप से कस्टम निर्देश के बिना संकलित किया जाएगा लेकिन अतिरिक्त विशेषताओं के साथ। मैं जो हासिल करने की कोशिश कर रहा था वह कस्टम निर्देश को हटाने और एक प्रक्रिया में इस सब को संभालने के लिए नहीं था: ऐसा नहीं किया जा सकता है, ऐसा लगता है। कृपया अपडेट किए गए plnkr: plnkr.co/edit/Q13bUt?p=preview को देखें
frapontillo

2
ध्यान दें कि यदि आपको संकलन या लिंक फ़ंक्शंस की विशेषताओं ऑब्जेक्ट पैरामीटर का उपयोग करने की आवश्यकता है, तो जानें कि विशेषता मानों को प्रक्षेपित करने के लिए जिम्मेदार निर्देश की प्राथमिकता 100 है, और आपके निर्देश की इससे कम प्राथमिकता होनी चाहिए, अन्यथा आपको केवल प्राप्त होगा निर्देशिका टर्मिनल होने के कारण विशेषताओं के स्ट्रिंग मान। देखें ( इस गीथब पुल अनुरोध और इस संबंधित मुद्दे को देखें )
सिमेन एखोल्ट

2
उन common-thingsसंपत्तियों को हटाने के विकल्प के रूप में, जो आप एक अधिकतम पैरामीटर पैरामीटर को कंपाइल कमांड पर भेज सकते हैं:$compile(element, null, 1000)(scope);
एंड्रियास

10

आप वास्तव में एक सरल टेम्पलेट टैग के साथ इस सब को संभाल सकते हैं। एक उदाहरण के लिए http://jsfiddle.net/m4ve9/ देखें । ध्यान दें कि मुझे वास्तव में सुपर-डायरेक्टिव परिभाषा पर एक संकलन या लिंक संपत्ति की आवश्यकता नहीं थी।

संकलन प्रक्रिया के दौरान, कोणीय संकलन से पहले टेम्प्लेट मानों में खींचता है, इसलिए आप वहां कोई और निर्देश संलग्न कर सकते हैं और कोणीय आपके लिए इसका ध्यान रखेगा।

यदि यह एक सुपर निर्देश है जिसे मूल आंतरिक सामग्री को संरक्षित करने की आवश्यकता है, तो आप उपयोग कर सकते हैं transclude : trueऔर अंदर से बदल सकते हैं<ng-transclude></ng-transclude>

आशा है कि मदद करता है, मुझे पता है कि अगर कुछ भी स्पष्ट नहीं है

एलेक्स


धन्यवाद एलेक्स, इस दृष्टिकोण के लिए समस्या यह है कि मैं टैग क्या होगा पर कोई धारणा नहीं बना सकता। उदाहरण में यह एक तिथि-निर्धारण, यानी एक inputटैग था, लेकिन मैं इसे किसी भी तत्व, जैसे divs या selects के लिए काम करना चाहूंगा ।
frapontillo

1
आह, हाँ, मुझे वह याद आया। उस मामले में मैं एक div के साथ चिपके रहने की सलाह दूंगा और यह सुनिश्चित करूंगा कि आपके अन्य निर्देश उस पर काम कर सकें। यह उत्तरों में सबसे साफ नहीं है, लेकिन कोणीय कार्यप्रणाली में सबसे उपयुक्त है। जब तक बूटस्ट्रैप प्रक्रिया एक HTML नोड को संकलित करना शुरू कर देती है, तब तक यह संकलन के लिए नोड पर पहले से ही सभी निर्देशों को एकत्र कर लेता है, इसलिए एक नया जोड़कर मूल बूटस्ट्रैप प्रक्रिया द्वारा ध्यान नहीं दिया जाएगा। आपकी आवश्यकताओं के आधार पर, आप एक div में सब कुछ लपेट सकते हैं और उसके भीतर काम करने से आपको अधिक लचीलापन मिलता है, लेकिन यह भी सीमित करता है कि आप अपने तत्व को कहां रख सकते हैं।
mrvdot

3
@frapontillo आप के साथ एक समारोह के रूप में एक टेम्पलेट का उपयोग कर सकते हैं elementऔर attrsमें पारित मुझे काम है कि बाहर करने के लिए उम्र लिया, और मैंने नहीं देखा है यह कहीं भी इस्तेमाल - लेकिन यह काम ठीक लगता है:। stackoverflow.com/a/20137542/1455709
पैट्रिक

6

यहां एक समाधान है जो उन निर्देशों को स्थानांतरित करता है जिन्हें गतिशील रूप से जोड़ने की आवश्यकता होती है, और कुछ वैकल्पिक (बुनियादी) सशर्त-तर्क भी जोड़ता है। यह बिना हार्ड-लॉजिक वाले निर्देश को साफ रखता है।

निर्देश वस्तुओं की एक सरणी लेता है, प्रत्येक ऑब्जेक्ट में निर्देश का नाम जोड़ा जाना है और इसे (यदि कोई हो) पास करने के लिए मूल्य।

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

मैं भी उपयोग कर रहा हूँ attrs.$attr.dynamicDirectivesके निर्देश (जैसे जोड़ने के लिए इस्तेमाल किया सटीक विशेषता घोषणा प्राप्त करने के लिए data-dynamic-directive, dynamic-directiveके लिए जाँच करने के लिए हार्ड-कोड स्ट्रिंग मूल्यों के बिना)।

Plunker Demo

angular.module('plunker', ['ui.bootstrap'])
    .controller('DatepickerDemoCtrl', ['$scope',
        function($scope) {
            $scope.dt = function() {
                return new Date();
            };
            $scope.selects = [1, 2, 3, 4];
            $scope.el = 2;

            // For use with our dynamic-directive
            $scope.selectIsRequired = true;
            $scope.addTooltip = function() {
                return true;
            };
        }
    ])
    .directive('dynamicDirectives', ['$compile',
        function($compile) {
            
             var addDirectiveToElement = function(scope, element, dir) {
                var propName;
                if (dir.if) {
                    propName = Object.keys(dir)[1];
                    var addDirective = scope.$eval(dir.if);
                    if (addDirective) {
                        element.attr(propName, dir[propName]);
                    }
                } else { // No condition, just add directive
                    propName = Object.keys(dir)[0];
                    element.attr(propName, dir[propName]);
                }
            };
            
            var linker = function(scope, element, attrs) {
                var directives = scope.$eval(attrs.dynamicDirectives);
        
                if (!directives || !angular.isArray(directives)) {
                    return $compile(element)(scope);
                }
               
                // Add all directives in the array
                angular.forEach(directives, function(dir){
                    addDirectiveToElement(scope, element, dir);
                });
                
                // Remove attribute used to add this directive
                element.removeAttr(attrs.$attr.dynamicDirectives);
                // Compile element to run other directives
                $compile(element)(scope);
            };
        
            return {
                priority: 1001, // Run before other directives e.g.  ng-repeat
                terminal: true, // Stop other directives running
                link: linker
            };
        }
    ]);
<!doctype html>
<html ng-app="plunker">

<head>
    <script src="//code.angularjs.org/1.2.20/angular.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>

<body>

    <div data-ng-controller="DatepickerDemoCtrl">

        <select data-ng-options="s for s in selects" data-ng-model="el" 
            data-dynamic-directives="[
                { 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
                { 'tooltip-placement' : 'bottom' },
                { 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
            ]">
            <option value=""></option>
        </select>

    </div>
</body>

</html>


एक अन्य निर्देशक टेम्पलेट में उपयोग किया जाता है। यह ठीक काम है और मेरा समय बचा है। बस धन्यवाद।
jcstritt

4

मैं अपना समाधान जोड़ना चाहता था क्योंकि स्वीकृत व्यक्ति मेरे लिए काफी काम नहीं करता था।

मुझे एक निर्देश जोड़ने की जरूरत थी लेकिन तत्व पर मेरा भी ध्यान रखें।

इस उदाहरण में मैं तत्व के लिए एक सरल एनजी-शैली निर्देश जोड़ रहा हूं। अनंत संकलन लूपों को रोकने के लिए और मुझे अपना निर्देश रखने की अनुमति देने के लिए मैंने एक चेक जोड़ा कि क्या मैंने जोड़ा है जो तत्व को फिर से जमा करने से पहले मौजूद था।

angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
    return {
        priority: 1001,
        controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {

            // controller code here

        }],
        compile: function(element, attributes){
            var compile = false;

            //check to see if the target directive was already added
            if(!element.attr('ng-style')){
                //add the target directive
                element.attr('ng-style', "{'width':'200px'}");
                compile = true;
            }
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {  },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    if(compile){
                        $compile(iElement)(scope);
                    }
                }
            };
        }
    };
}]);

यह ध्यान देने योग्य है कि आप इसका उपयोग ट्रांसक्लूड या टेम्पलेट के साथ नहीं कर सकते हैं, क्योंकि कंपाइलर उन्हें दूसरे दौर में फिर से लागू करने का प्रयास करता है।
11

1

तत्व पर एक विशेषता में राज्य को संग्रहीत करने का प्रयास करें, जैसे कि superDirectiveStatus="true"

उदाहरण के लिए:

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        var status = element.attr('superDirectiveStatus');
        if( status !== "true" ){
             element.attr('datepicker', 'someValue');
             element.attr('datepicker-language', 'en');
             // some more
             element.attr('superDirectiveStatus','true');
             $compile(element)(scope);

        }

      }
    };
  });

मैं आशान्वित हूं कि इससे आपको सहायता मिलेगी।


धन्यवाद, मूल अवधारणा एक ही रहती है :)। मैं पहले संकलन पास को छोड़ने का एक तरीका निकालने की कोशिश कर रहा हूं। मैंने मूल प्रश्न अपडेट कर दिया है।
फ़्राँपॉन्टिलो

दोहरा संकलन एक भयानक तरीके से चीजों को तोड़ता है।
frapontillo

1

1.3.x से 1.4.x में परिवर्तन हुआ था।

कोणीय 1.3.x में यह काम करता है:

var dir: ng.IDirective = {
    restrict: "A",
    require: ["select", "ngModel"],
    compile: compile,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
        attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
        scope.akademischetitel = AkademischerTitel.query();
    }
}

अब कोणीय 1.4.x में हमें यह करना है:

var dir: ng.IDirective = {
    restrict: "A",
    compile: compile,
    terminal: true,
    priority: 10,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");
    tElement.removeAttr("tq-akademischer-titel-select");
    tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {

        $compile(element)(scope);
        scope.akademischetitel = AkademischerTitel.query();
    }
}

(स्वीकृत उत्तर से: https://stackoverflow.com/a/19228302/605586 खान से TO)।


0

एक सरल समाधान जो कुछ मामलों में काम कर सकता है, वह है रैपर को बनाना और उसका संकलन करना और उसके बाद अपने मूल तत्व को जोड़ना।

कुछ इस तरह...

link: function(scope, elem, attr){
    var wrapper = angular.element('<div tooltip></div>');
    elem.before(wrapper);
    $compile(wrapper)(scope);
    wrapper.append(elem);
}

इस समाधान का यह फायदा है कि यह मूल तत्व को फिर से जमा न करके चीजों को सरल रखता है।

यदि जोड़े गए निर्देशों में से requireकोई भी मूल तत्व के निर्देशों में से कोई भी काम नहीं करेगा या यदि मूल तत्व की पूर्ण स्थिति है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.