Soru AngularJS: Bir yönergede küresel bir etkinliğe bağlamanın en iyi yolu nedir?


Küresel bir olaya cevap vermesi gereken bir yönerge oluşturmak istediğiniz AngularJS'deki durumu düşünün. Bu durumda, pencere yeniden boyutlandırma olayını diyelim.

Bunun için en iyi yaklaşım nedir? Gördüğüm gibi iki seçeneğimiz var: 1. Her direktifin olaya bağlanmasına izin verin ve şu andaki element üzerinde sihir yapın 2. Mantık uygulamasının uygulanması gereken her öğeyi almak için bir DOM seçici kullanan bir genel olay dinleyicisi oluşturun.

Seçenek 1, bazı işlemleri yapmak istediğiniz öğeye zaten erişiminizin avantajına sahiptir. Ancak ... seçenek 2, aynı etkinlikteki bir performans yararı olabilecek birden fazla kez (her yönerge için) bağlamanıza gerek kalmaması avantajına sahiptir.

Her iki seçeneği de gösterelim:

Seçenek 1:

angular.module('app').directive('myDirective', function(){

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             angular.element(window).on('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

Seçenek 2:

angular.module('app').directive('myDirective', function(){

    function doSomethingFancy(){
         var elements = document.querySelectorAll('[my-directive]');
         angular.forEach(elements, function(el){
             // In here we have our operations on the element
         });
    }

    return {
        link: function(scope, element){
             // Maybe we have to do something in here, maybe not.
        }
    };

    // Bind to the window resize event only once.
    angular.element(window).on('resize', doSomethingFancy);
});

Her iki yaklaşım da gayet iyi çalışıyor ama bu iki seçeneğin gerçekten 'Açısal-ish' olmadığını düşünüyorum.

Herhangi bir fikir?


60
2018-04-24 14:41


Menşei




Cevaplar:


Pencere yeniden boyutlandırma gibi global olayları etkili bir şekilde konumlandırmak için başka bir yöntem seçtim. Javascript olaylarını başka bir yönerge ile Açısal kapsam olaylarına dönüştürür.

app.directive('resize', function($window) {
  return {
    link: function(scope) {
      function onResize(e) {
        // Namespacing events with name of directive + event to avoid collisions
        scope.$broadcast('resize::resize');
      }

      function cleanUp() {
        angular.element($window).off('resize', onResize);
      }

      angular.element($window).on('resize', onResize);
      scope.$on('$destroy', cleanUp);
    }
  }
});

Temel durumda, uygulamanın kök öğesinde kullanılabilir

<body ng-app="myApp" resize>...

Ve sonra etkinliği diğer direktiflerde dinle

<div my-directive>....

kodlanmış olarak:

app.directive('myDirective', function() {
  return {
    link: function(scope, element) {
      scope.$on('resize::resize', function() {
        doSomethingFancy(element);
      });
    });
  }
});

Bunun diğer yaklaşımlara göre birtakım faydaları vardır:

  • Direktiflerin nasıl kullanıldığı konusunda kesin bir şekilde kırılmamalıdır. Seçenek 2'nin gerektirdiği my-directive açısal olarak aşağıdaki eşdeğer olarak davranır: my:directive, data-my-directive, x-my-directive, my_directive içinde görüldüğü gibi direktifler rehberi

  • Javascript olayının, tüm dinleyicileri etkileyen Açısal olaya nasıl dönüştürüldüğünü tam olarak etkileyecek tek bir yeriniz var. Daha sonra javascript'i geri almak istediğinizi söyleyin resize kullanarak Lodash debounce işlevi. Değiştirebilirsin resize yönerge:

    angular.element($window).on('resize', $window._.debounce(function() {
      scope.$broadcast('resize::resize');
    },500));
    
  • Çünkü olayları mutlaka ateşlemiyor $rootScope, olayları yalnızca uygulamanızın bir parçası olarak kısıtlayabilirsiniz. resize direktif

    <body ng-app="myApp">
      <div>
        <!-- No 'resize' events here -->
      </div>
      <div resize>
        <!-- 'resize' events are $broadcast here -->
      </div>
    
  • Yönergeyi seçeneklerle genişletebilir ve uygulamanızın farklı bölümlerinde farklı şekilde kullanabilirsiniz. Farklı parçalarda farklı sürümleri istediğinizi varsayalım:

    link: function(scope, element, attrs) {
      var wait = 0;
      attrs.$observe('resize', function(newWait) {
        wait = $window.parseInt(newWait || 0);
      });
      angular.element($window).on('resize', $window._.debounce(function() {
        scope.$broadcast('resize::resize');
      }, wait));
    }
    

    Olarak kullanılan:

    <div resize>
      <!-- Undebounced 'resize' Angular events here -->
    </div>
    <div resize="500">
      <!-- 'resize' is debounced by 500 milliseconds -->
    </div>
    
  • Daha sonra yönergeyi yararlı olabilecek diğer etkinliklerle genişletebilirsiniz. Belki gibi şeyler resize::heightIncrease. resize::heightDecrease, resize::widthIncrease, resize::widthDecrease. Uygulamanızda pencerenin tam boyutlarını hatırlama ve işleme ile ilgilenen tek bir yere sahip olursunuz.

  • Verileri olaylarla birlikte iletebilirsiniz. Çapraz tarayıcı sorunları ile uğraşmanız gerekebilecek viewport yüksekliği / genişliği gibi bir şey söyleyin (IE desteğine ne kadar ihtiyaç duyduğunuza ve size yardımcı olacak başka bir kitaplık ekleyip eklemediğinize bağlı olarak).

    angular.element($window).on('resize', function() {
      // From http://stackoverflow.com/a/11744120/1319998
      var w = $window,
          d = $document[0],
          e = d.documentElement,
          g = d.getElementsByTagName('body')[0],
          x = w.innerWidth || e.clientWidth || g.clientWidth,
          y = w.innerHeight|| e.clientHeight|| g.clientHeight;
      scope.$broadcast('resize::resize', {
        innerWidth: x,
        innerHeight: y
      });
    });
    

    Bu, daha sonra verilere eklemek için tek bir yer verir. Örneğin. Son debounced olaydan bu yana boyuttaki farkı göndermek ister misin? Büyüklüğü hatırlamak ve farkı göndermek için muhtemelen biraz kod ekleyebilirsiniz.

Esasen bu tasarım, yapılandırılabilir bir şekilde, global Javascript olaylarını yerel Angular olaylarına ve yalnızca bir uygulamaya değil, uygulamanın direktifine bağlı olarak bir uygulamanın farklı bölümlerine yerel olarak dönüştürmenin bir yolunu sunar.


134
2018-04-27 13:08



Bu. Çok iyi bir yaklaşım. Yine de biraz ağır olabilir, ancak birinin ekstra işlevselliğe ihtiyacı yoksa basitleştirilebilir. - J_A_X
Küçük bir düzenleme göndermeyi denedim ancak reddedildi. (Fantastik) cevabınızda sadece küçük bir hata var. Parametre scope değişkenle uyuşmuyor $scope. Hayır biggie. Düzenleme: Düzenlemeyi zaten gönderiden güncellediğinizi görüyorum. Disregard :) - Pierce
Harika cevap, teşekkür ederim! Böyle bir şey yapmak istiyordum, ama tüm frosting'i kek üzerine koymuş olan çok güzel fikirlerle karşılaştın. Nasıl dahil edileceğine değinmek gerekir $scope.off('resize::resize') ve angular.element($window).off('resize') ve daha sonra onları yeniden etkinleştirmek (veya eğer gerekli ise (neden ve neden olmasın)) ve sonra bunları bir blog yazısına ya da bir bower bileşenine atmak. Tekrar teşekkürler, TIL çok! - NominalAeon
olduğu :: salt okunur ile ilgili? Google'a bir yön verebilir misiniz? - Saksham
@Saksham Hayır, sadece çarpışmalardan kaçınmaya çalışmak için olayları adlandırma yolu. Bir kez bağlanma ile ilgisiz. - Michal Charemza


Bir çerçevenin en üstünde gelişirken, sıklıkla bir problemi ortaya çıkarmadan önce bir problem hakkında agnostik düşünmeyi faydalı buluyorum. "Ne" ve "neden" diye cevap vermek "nasıl" olduğunu ortaya koymaktadır.

Buradaki cevap gerçekten karmaşıklığa bağlıdır. doSomethingFancy(). Bu direktifin örnekleriyle ilişkili veriler, bir dizi işlevsellik veya etki alanı nesnesi var mı? Ayarlanması gibi tamamen bir sunum kaygısı var mı width veya height Bazı elemanların özellikleri pencere boyutunun uygun oranlarına mı? İş için doğru aracı kullandığınızdan emin olun; iş, cımbızları çağırdığında bütün İsviçre Ordusu bıçağını getirmeyin ve tek başına bir çiftinize erişebilirsiniz. Bu yolda devam etmek için, ben varsayım ile çalışacağım doSomethingFancy() tamamen sunum işlevidir.

Küresel bir tarayıcı olayını Açısal bir olayda sarmanın endişesi, bazı basit çalışma aşaması yapılandırmasıyla ele alınabilir:

angular.module('myApp')
    .run(function ($rootScope) {
        angular.element(window).on('resize', function () {
            $rootScope.$broadcast('global:resize');  
        })
    })
;

Şimdi Açısal, her bir yönerge ile ilgili tüm çalışmaları yapmak zorunda değildir. $digest, ama aynı işlevi alıyorsunuz.

İkinci endişe devam ediyor n Bu olay tetiklendiğinde öğe sayısı. Yine, eğer bir direktifin tüm çırpma ve ıslıklarına ihtiyacınız yoksa, bunun gerçekleşmesi için başka yollar da vardır. Yaklaşımı yukarıdaki blokta genişletebilir veya uyarlayabilirsiniz:

angular.module('myApp')
    .run(function () {
        angular.element(window).on('resize', function () {
            var elements = document.querySelectorAll('.reacts-to-resize');
        })
    })
;

Eğer sen yap Yeniden boyutlandırma olayında gerçekleşmesi gereken daha karmaşık bir mantığa sahip olmak, yine de bir veya daha fazla direktifin onu işlemenin en iyi yolu olduğu anlamına gelmez. Yukarıda anonim çalıştırma aşaması yapılandırması yerine örneklenen basit bir aracı hizmetini kullanabilirsiniz:

/**
 * you can inject any services you want: $rootScope if you still want to $broadcast (in)
 * which case, you'd have a "Publisher" instead of a "Mediator"), one or more services 
 * that maintain some domain objects that you want to manipulate, etc.
 */
function ResizeMediator($window) {
    function doSomethingFancy() {
        // whatever fancy stuff you want to do
    }

    angular.element($window).bind('resize', function () {
        // call doSomethingFancy() or maybe some other stuff
    });
}

angular.module('myApp')
    .service('resizeMediator', ResizeMediator)
    .run(resizeMediator)
;

Şimdi, birim test edilebilen, ancak kullanılmayan yürütme aşamalarını çalıştırmayan kapsüllenmiş bir hizmetimiz var.

Karar vermeyi de etkileyen bir çift endişe:

  • Ölü dinleyici - Seçenek 1 ile, yönergelerin her örneği için en az bir olay dinleyici oluşturuyorsunuz. Bu öğeler DOM'a dinamik olarak eklenirse veya kaldırılırsa ve siz aramazsanız $on('$destroy')Öğeleri artık yokken kendilerini uygulayan olay işleyicileri riskini kullanıyorsunuz.
  • Genişlik / yükseklik operatörlerinin performansı - Küresel olayın yeniden boyutlandırılması gerektiğinden, kutu model mantığının var olduğunu farz ediyorum. Değilse, bunu görmezden gel; eğer öyleyse, hangi özellikleri kullandığınıza ve ne sıklıkta olduğuna dikkat etmek istersiniz, çünkü tarayıcı yeniden akışları bir performans düşüşünde büyük suçlu.

Bu cevabın beklediğiniz gibi "Açısal" olması muhtemeldir, ama kutu-model-sadece mantığının eklenmiş varsayımıyla anladığım kadarıyla sorunu çözme biçimim.


6
2018-04-27 19:07



Aslında istediğim gibi 'açısal-ish' değil ama kesinlikle iyi bir yaklaşım. Paylaşım için teşekkürler. - Bas Slagter


Benim görüşüme göre, 1 numaralı yöntemle ve $ window hizmetini kullanarak küçük bir değişiklik yapacağım.

angular.module('app').directive('myDirective', function($window){

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             anguar.element($window).bind('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

2. Bu yaklaşıma ve burada düşünme konusunda ufak bir değişikliğe atıfta bulunarak - bu olay dinleyicisini app.run'da bir yere yükseltebilirsiniz - ve olay gerçekleştiğinde, buradaki yönergenin aldığı ve başka bir şeyin yayınlandığı başka bir etkinlik yayınlayabilirsiniz. olay gerçekleşir.

DÜZENLE: Bu yöntem hakkında ne kadar çok düşünürsem, ilkinden daha çok hoşlanmaya başlarım ... Pencereyi yeniden boyutlandırmak için harika bir yol olan yeniden boyutlandırma etkinliği - belki gelecekte başka bir şeyin de bu bilgiyi "bilmesi" gerekiyor. Böyle bir şey yapmazsanız, kurmaya zorlanırsınız - bir kez daha - window.resize olayına başka bir olay dinleyicisi.

app.run

app.run(function($window, $rootScope) {
  angular.element($window).bind('resize', function(){
     $rootScope.$broadcast('window-resize');
  });
}

Direktif     angular.module ('app') yönerge ('myDirective', işlev ($ rootScope) {

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             $rootScope.$on('window-resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

En sonunda Nasıl yapılacağına dair harika bir kaynak, örn. ui-önyükleme. Bu adamlardan nasıl bir şeyler öğrenileceğini öğrendim, örneğin öğrenme sınavının öğrenme öykülerini açısal olarak. Ödeme için harika bir temiz kod tabanı sağlarlar.


3
2018-04-27 07:15



UI-bootstrap'e dikkat etmek iyi bir şey! - Bas Slagter
Her seferinde garip şeyler yaparlar, ama onlar bir sürü osuruk avcısıdır, bu yüzden onlardan öğrenebileceğimize eminim. Bu yazı gerçekten ui modüllerinin binasına giden çılgın düşünceyi gösteriyor! joelhooks.com/blog/2014/02/11/... Serin şeyler - ve Açısal ve parçalarını yorumlamak için farklı bir yol. - Sten Muchow


İkinci yaklaşım daha kırılgan hissettirir, çünkü Angular şablondaki yönergeye başvurmak için birçok yol sunar (my-directive, my_directive, my:directive, x-my-directive, data-my-directivevs.) böylece hepsini kapsayan bir CSS seçici gerçekten karmaşık olabilir.

Direktifleri sadece dahili olarak kullanırsanız veya tek bir kelimeden oluşuyorsa, bu büyük bir anlaşma değildir. Ancak, diğer geliştiriciler (farklı kodlama kuralları ile) yönergelerinizi kullanıyorsa, ikinci yaklaşımdan kaçınmak isteyebilirsiniz.

Ama pragmatik olurdum. Bir avuç örnekle uğraşıyorsanız # 1 ile devam edin. Eğer yüzlerce varsa, # 2 ile giderdim.


1
2018-04-27 07:18



Tamamen katılıyorum. Ayrıca, seçenek 2'nin projedeki yeni geliştiricilere açıklamak için biraz daha zor olduğunu hayal edebiliyorum. Ama bazen sadece en performanslı çözümle gitmek istersiniz. - Bas Slagter


İşte bunu yapmanın bir yolu, sadece elemanlarınızı bir dizide saklayın, sonra "global etkinlik" elemanlar arasında dolaşabilir ve yapmanız gerekenleri yapabilirsiniz.

angular.module('app').directive('myDirective', function($window){

    var elements = [];

    $window.on('resize', function(){
       elements.forEach(function(element){
           // In here we have our operations on the element
       });
    });

    return {
        link: function(scope, element){
            elements.push(element);
        }
    };
});

1
2018-05-03 15:55