web-dev-qa-db-ja.com

AngularJS:ディレクティブでグローバルイベントにバインドする最良の方法は何ですか

グローバルイベントに応答する必要があるディレクティブを作成したいAngularJSの状況を想像してください。この場合、ウィンドウのサイズ変更イベントとしましょう。

これに最適なアプローチは何ですか?私の見方では、2つのオプションがあります。1.すべてのディレクティブをイベントにバインドし、現在の要素でそれを魔法にします。適用されます。

オプション1には、一部の操作を実行する要素に既にアクセスできるという利点があります。しかし...オプション2には、同じイベントに(ディレクティブごとに)複数回バインドする必要がないという利点があり、パフォーマンス上のメリットがあります。

両方のオプションを説明しましょう:

オプション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);
             });
        }
    };
});

オプション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);
});

どちらのアプローチも正常に機能していますが、オプション2は実際には「角っぽい」ものではないと感じています。

何か案は?

61
Bas Slagter

フレームワーク上で開発する場合、慣用句を設計する前に、問題について不可知論的に考えることが役立つことがよくあります。 「何」と「理由」に答えることで、「方法」が決まります。

ここでの答えは、doSomethingFancy()の複雑さに本当に依存します。このディレクティブのインスタンスに関連付けられたデータ、機能セット、またはドメインオブジェクトはありますか?特定の要素のwidthまたはheightプロパティをウィンドウサイズの適切な割合に調整するなど、純粋に表示上の問題ですか?ジョブに適切なツールを使用していることを確認してください。仕事でピンセットが必要で、スタンドアロンのペアにアクセスできるときに、スイスアーミーナイフ全体を持ち込まないでください。この流れを継続するために、doSomethingFancy()は純粋に表示機能であるという前提で動作します。

Angularイベントでグローバルブラウザーイベントをラップする懸念は、いくつかの単純な実行フェーズ構成によって処理できます。

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

Angularは、各$digestのディレクティブに関連付けられたすべての作業を行う必要はありませんが、同じ機能を取得しています。

2番目の問題は、このイベントが発生したときにn個の要素を操作することです。繰り返しになりますが、ディレクティブのすべての添え字が必要でない場合は、これを実現する他の方法があります。上記の実行ブロックでアプローチを拡張または適応させることができます。

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

サイズ変更イベントで発生する必要があるより複雑なロジックがdoある場合でも、1つ以上のディレクティブが最適な方法であることを必ずしも意味しません扱う。前述の匿名実行フェーズ構成の代わりに、インスタンス化される単純なメディエーターサービスを使用できます。

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

これで、ユニットテストできるカプセル化されたサービスができましたが、未使用の実行フェーズは実行されません。

決定の要因となるいくつかの懸念:

  • デッドリスナー-オプション1では、ディレクティブのインスタンスごとに少なくとも1つのイベントリスナーを作成しています。これらの要素がDOMに動的に追加または削除され、$on('$destroy')を呼び出さない場合、要素が存在しなくなったときにイベントハンドラーが自身を適用するリスクがあります。
  • 幅/高さ演算子のパフォーマンス-グローバルイベントがブラウザのサイズ変更であるとすると、ここにはボックスモデルロジックがあると仮定しています。そうでない場合は、これを無視してください。その場合、ブラウザのリフローは パフォーマンスの低下の大きな原因 になる可能性があるため、アクセスするプロパティと頻度に注意する必要があります。

この答えはあなたが望んでいたほど「角度」ではない可能性がありますが、ボックスモデルのみのロジックの追加された仮定でそれを理解するので、私は問題を解決する方法です。

6
user128268

私の意見では、方法#1と、$ windowサービスを使用する少しの微調整を行います。

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このアプローチとここでの考え方のわずかな変更に関連して、このイベントリスナーをapp.runの上位に置くことができます。イベントが発生すると、別のイベントをブロードキャストできます。このイベントは、ディレクティブが取得し、そのイベントが発生したときに空想的なことを行います。

EDIT:この方法について考えれば考えるほど、最初の方法よりも実際に好きになり始めます。ウィンドウのサイズ変更イベント-将来的には他の何かがこの情報を「知る」必要があり、このようなことをしない限りセットアップを強制されます-まだ-別のイベントwindow.resizeイベントのリスナー。

app.run

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

Directiveangular.module( 'app')。directive( 'myDirective'、function($ 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);
             });
        }
    };
});

最後に物事を行うための素晴らしいソースは、たとえば i-bootstrap のようなアンギュラーUIをフォローすることです。私はこれらの人たちから詰め込み方をたくさん学びました。例えば、角度でユニットテストを学ぶことの喜びです。彼らはチェックアウトするための素晴らしいきれいなコードベースを提供します。

3
Sten Muchow

これを行う方法の1つは、要素を配列に保存してから、"global event"に要素をループして、必要なことを実行できることです。

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
iConnor

Angularはテンプレート内のディレクティブを参照する多くの方法を提供するため、2番目のアプローチはより脆弱に感じます(my-directivemy_directivemy:directivex-my-directivedata-my-directiveなど)、それらすべてをカバーするCSSセレクターは本当に複雑になる可能性があります。

ディレクティブを内部でのみ使用する場合、またはディレクティブが単一のWordで構成される場合、これはおそらく大した問題ではありません。ただし、他の開発者(コーディング規約が異なる)がディレクティブを使用している可能性がある場合は、2番目のアプローチを避けたい場合があります。

しかし、私は実際的です。少数のインスタンスを扱っている場合は、#1に進みます。あなたがそれらの数百を持っている場合、私は#2に行きます。

1
Stepan Riha