AngularJS'deki yönergeden yönergeler ekleyin


197

Üzerinde ilan edilen öğeye daha fazla direktif eklemeye özen gösteren bir direktif oluşturmaya çalışıyorum . Örneğin, ben ekleyerek ilgilenir bir direktif kurmak istiyorum datepicker, datepicker-languageve ng-required="true".

Bu öznitelikleri eklemek ve sonra kullanmak $compileAçıkçası sonsuz bir döngü oluşturmak, bu yüzden zaten gerekli öznitelikleri eklemiş olup olmadığını kontrol ediyorum:

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

Tabii ki, $compileöğeyi yapmazsam , özellikler ayarlanır, ancak yönerge önyükleme yapmaz.

Bu yaklaşım doğru mu yoksa yanlış mı yapıyorum? Aynı davranışı elde etmenin daha iyi bir yolu var mı?

UDPATE : Bunu $compilebaşarmanın tek yolu olduğu düşünüldüğünde, ilk derleme geçişini atlamanın bir yolu var mı (öğe birkaç çocuk içerebilir)? Belki ayarlayarak terminal:true?

GÜNCELLEME 2 : Yönergeyi bir selectöğeye koymaya çalıştım ve beklendiği gibi derleme iki kez çalışır, bu da beklenen options sayısının iki katı olduğu anlamına gelir .

Yanıtlar:


260

Tek bir DOM öğesinde birden fazla yönerge bulunduğunuz ve uygulanma prioritysırasının önemli olduğu durumlarda, uygulamalarını sipariş etmek için özelliği kullanabilirsiniz . Önce yüksek sayılar çalışır. Birini belirtmezseniz, varsayılan öncelik 0'dır.

EDIT : tartışmadan sonra, işte tam bir çözüm. Anahtar :: özniteliğini kaldırmaktıelement.removeAttr("common-things"); ve ayrıca element.removeAttr("data-common-things");(kullanıcıların html'de belirtmesi durumunda data-common-things)

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

Çalışan dalma pistonunu şu adreste bulabilirsiniz: http://plnkr.co/edit/Q13bUt?p=preview

Veya:

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

DEMO

Biz sete neden açıklanması terminal: trueve priority: 1000(yüksek sayıda):

DOM hazır olduğunda, açısal, tüm kayıtlı direktifleri tanımlamak ve priority bu direktiflerin aynı eleman üzerinde olup olmadığına bağlı olarak direktifleri tek tek derlemek için DOM'u yürür . İlk önce derlenmesini sağlamak için özel direktifimizin önceliğini yüksek bir sayıya ayarladık ve bu direktif derlendikten sonra terminal: truediğer direktifler atlanacaktır .

Özel yönergemiz derlendiğinde, yönergeler ekleyerek ve kendisini kaldırarak öğeyi değiştirir ve tüm yönergeleri (atlananlar dahil) derlemek için $ compile hizmetini kullanır .

Ayarlamazsak terminal:trueve priority: 1000bazı direktiflerin özel direktifimizden önce derlenme şansı vardır . Özel yönergemiz, = derlemek için $ compile kullandığında, zaten derlenmiş yönergeleri yeniden derlemektedir. Bu, özellikle özel yönergemizden önce derlenen yönergeler zaten DOM'u dönüştürdüyse öngörülemeyen davranışlara neden olacaktır.

Öncelik ve terminal hakkında daha fazla bilgi için Direktifin “terminali” nasıl anlaşılır?

Şablonu da değiştiren bir yönerge örneği ng-repeat(öncelik = 1000), ng-repeatderlendiğinde, ng-repeat diğer yönergeler uygulanmadan önce şablon öğesinin kopyalarını oluşturun .

@ Izhaki'nin yorumu sayesinde, ngRepeatkaynak koduna referans : https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js


5
Bana bir yığın taşması istisnası atar: RangeError: Maximum call stack size exceededsonsuza kadar derlemeye devam ederken.
frapontillo

3
@frapontillo: sizin durumunuzda, element.removeAttr("common-datepicker");belirsiz döngüden kaçınmak için eklemeyi deneyin .
Khanh TO

4
Tamam, bunu çözmek mümkün oldum, sen sete sahip replace: false, terminal: true, priority: 1000; sonra compileişlevde istenen nitelikleri ayarlayın ve yönerge niteliğimizi kaldırın. Son olarak, postdöndürülen işlevde compileçağırın $compile(element)(scope). Öğe, özel yönerge olmadan ancak eklenen özniteliklerle düzenli olarak derlenecektir. Ulaşmaya çalıştığım, özel yönergeyi kaldırmak ve tüm bunları tek bir süreçte ele almak değildi: Bu yapılamaz, öyle görünüyor. Lütfen güncellenmiş plnkr'ye bakın: plnkr.co/edit/Q13bUt?p=preview .
frapontillo

2
Derleme veya bağlantı işlevlerinin attributes nesnesi parametresini kullanmanız gerekirse, özellik değerlerini enterpolasyondan sorumlu direktifin 100 önceliğe sahip olduğunu ve direktifinizin bundan daha düşük bir önceliğe sahip olması gerektiğini unutmayın, aksi takdirde yalnızca dizinin terminal olması nedeniyle özniteliklerin dize değerleri. Bkz. ( Bu github çekme isteğine ve ilgili konuya bakın )
Simen Echholt

2
common-thingsnitelikleri kaldırmaya alternatif olarak derleme komutuna bir maxPriority parametresi iletebilirsiniz:$compile(element, null, 1000)(scope);
Andreas

10

Tüm bunları basit bir şablon etiketi ile halledebilirsiniz. Örnek için http://jsfiddle.net/m4ve9/ adresine bakın . Aslında süper yönerge tanımında bir derleme veya bağlantı özelliğine ihtiyacım olmadığını unutmayın.

Derleme işlemi sırasında Açısal, derlemeden önce şablon değerlerini çeker, böylece oraya başka yönergeler ekleyebilirsiniz ve Açısal sizin için ilgilenecektir.

Bu, orijinal dahili içeriği koruması gereken bir süper yönerge ise transclude : true, iç kısmı<ng-transclude></ng-transclude>

Umarım yardımcı olur, net olmayan bir şey varsa bana bildirin

Alex


Teşekkürler Alex, bu yaklaşımın sorunu, etiketin ne olacağına dair herhangi bir varsayımda bulunamayacağım. Örnekte bir tarih seçici, yani bir inputetiket vardı, ama ben divs veya selects gibi herhangi bir öğe için çalışmasını istiyorum .
frapontillo

1
Ah, evet, bunu özledim. Bu durumda bir div'a bağlı kalmanızı ve diğer direktiflerinizin bunun üzerinde çalışabilmesini sağlamanızı öneririm. En temiz cevaplar değil, Açısal metodolojiye en uygun olanı. Önyükleme işlemi bir HTML düğümünü derlemeye başladığında, derleme için düğümdeki tüm yönergeleri zaten topladı, böylece yeni bir tane eklemek orijinal önyükleme işlemi tarafından fark edilmeyecek. İhtiyaçlarınıza bağlı olarak, her şeyi bir div'a kaydırıp daha fazla esneklik sağlayan içinde çalışabilirsiniz, ancak öğenizi nereye koyabileceğinizi de sınırlar.
mrvdot

3
@frapontillo Sen bir fonksiyonu olarak bir şablon kullanabilirsiniz elementve attrsgeçirilen çalışmaları olduğuna dikkat me yaş aldı ve bunu her yerde kullanılan görmedim - ama bir işe cezası görünüyor. stackoverflow.com/a/20137542/1455709
Patrick

6

Dinamik olarak eklenmesi gereken yönergeleri görünüme taşıyan ve isteğe bağlı (temel) koşullu mantık ekleyen bir çözüm. Bu, yönergeyi sabit kodlanmış bir mantık olmadan temiz tutar.

Yönerge bir dizi nesne alır, her nesne eklenecek yönerge adını ve ona iletilecek değeri (varsa) içerir.

Sadece aşağıdaki koşullara dayalı bir direktif ekleyen bazı koşullu mantık eklemenin yararlı olabileceğini düşünene kadar (aşağıdaki cevap hala geçerli olsa da) böyle bir direktif için bir kullanım örneği düşünmeye çalışıyordum. ifYönergenin eklenip eklenmeyeceğini belirleyen bir bool değeri, ifade veya işlev (örneğin, denetleyicinizde tanımlanmış) içermesi gereken isteğe bağlı bir özellik ekledim.

Ayrıca kontrol etmek için sabit kodlama dize değerleri olmadan attrs.$attr.dynamicDirectivesyönerge (örn data-dynamic-directive. dynamic-directive) Eklemek için kullanılan kesin öznitelik bildirimi almak için kullanıyorum .

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>


Başka bir yönerge şablonunda kullanılır. Bu sadece iyi iş ve zamandan tasarruf. Sadece teşekkürler.
jcstritt

4

Kabul ettiğim çözüm benim için pek işe yaramadığından çözümümü eklemek istedim.

Bir yönerge eklemem gerekiyordu, aynı zamanda benimkini de tutmam gerekiyordu.

Bu örnekte, öğeye basit bir ng tarzı yönerge ekliyorum. Sonsuz derleme döngülerini önlemek ve direktifimi korumama izin vermek için, öğeyi derlemeden önce eklediğim şeyin mevcut olup olmadığını görmek için bir kontrol ekledim.

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

Derleyici ikinci turda yeniden uygulamaya çalıştığından bunu transclude veya bir şablonla kullanamayacağınızı belirtmek gerekir.
spikyjt

1

Durumu, öğenin kendisindeki bir özellikte depolamayı deneyin, örneğin: superDirectiveStatus="true"

Örneğin:

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

        }

      }
    };
  });

Umarım bu sana yardımcı olur.


Teşekkürler, temel kavram aynı kalır :). İlk derleme kartını atlamanın bir yolunu bulmaya çalışıyorum. Orijinal soruyu güncelledim.
frapontillo

Çift derleme işleri korkunç bir şekilde kırar.
frapontillo

1

1.3.x'ten 1.4.x'e bir değişiklik oldu.

Açısal 1.3.x'te bu işe yaradı:

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

Şimdi Açısal 1.4.x'te bunu yapmalıyız:

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

(Kabul edilen cevaptan: Khanh TO'dan https://stackoverflow.com/a/19228302/605586 ).


0

Bazı durumlarda işe yarayabilecek basit bir çözüm, bir sarıcı oluşturmak ve $ derlemek ve daha sonra orijinal öğenizi buna eklemektir.

Gibi bir şey...

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

Bu çözüm, orijinal öğeyi yeniden derlemeyerek işleri basit tutması avantajına sahiptir.

Eklenen yönergelerden requireherhangi biri orijinal öğenin yönergelerinden herhangi birine sahipse veya orijinal öğenin mutlak konumlandırması varsa bu işe yaramaz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.