プログラマブログ

by wacul

menu
  • プログラマ
  • AngularJSで全てのng-clickに2重クリック防止機能を付ける

2015.02.17AngularJSで全てのng-clickに2重クリック防止機能を付ける

概要

Angularの標準のdirectiveであるng-clickに、返り値がpromiseの場合に解決されるまでクリックを防止する機能を付けてみました (ボタンを押したら1秒間クリックが無効化されるデモ)。 とても簡単に実装できるのでちょっと紹介してみます。

実装

とりあえず元の実装を見てみましょう(githubのソース)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 以下compile部分のみ抜き出し
compile: function($element, attr) {
  // 具体的なdirectiveNameは"ngClick"でng-click="hoge()"のhoge()部分を関数としてパースする
  var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
  return function ngEventHandler(scope, element) {
    // 具体的なeventNameは"click"
    element.on(eventName, function(event) {
      var callback = function() {
        fn(scope, {$event:event});
      };
      // クリックしたら単純に関数を実行してDOMに反映させる
      if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
        scope.$evalAsync(callback);
      } else {
        scope.$apply(callback);
      }
    });
  };
}

元の実装は単純にクリックイベントをハンドリングして登録された関数を実行して、DOMに反映するだけです。 これを$provide.decoratorを使用して実装を置き換えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
angular.module('foo', [])
.config(function($provide) {
  // directiveを置き換える場合はdirective名の末尾に"Directive"を付ける
  $provide.decorator('ngClickDirective', function($delegate, $parse, $q) {
    // $delegate[0]がdirective本体
    $delegate[0].compile = function($element, attr) {
      var fn = $parse(attr.ngClick, null, true);

      return function(scope, element) {
        element.on('click', function(event) {
          var result, promise, d;
          // el.disabled==trueのときにはクリック無効(disabledが使えない要素用の保険)
          if (element[0].disabled) return;
          result = fn(scope, {$event: event});
          // promiseの場合は解決を待つ
          if (result != null && typeof result.then === 'function') {
            promise = result;
          } else {
            d = $q.defer();
            d.resolve();
            promise = d.promise;
          }
          // promise解決までクリックを無効化して、解決したら有効化する
          element[0].disabled = true;
          function enable() {
            element[0].disabled = false;
          }
          promise.then(enable, enable);
        });
      };
    };
    return $delegate;
  });
})

登録された関数の返り値がpromiseの場合は解決するまで要素のdisalbedプロパティをtrueにしておいて、 解決されたらdisabledプロパティをfalseに戻します。 関数の返り値がpromiseでなくても解決済みのpromiseとしておくと統一的に扱えて便利です(ついでにDOMへの反映も自動でやってくれる!)。

これで、いままでのng-clickの部分を変更せずにクリック連打を防止することができます (もちろん、モジュールを読み込む必要はあります)。

1
angular.moduel('bar', ['foo']).controller('Ctrl', function() {...});

まとめ

ちょっと黒魔術っぽいんですが、ng-clickの実装を置き換えてボタン連打防止機能を付けたらとても便利だったので紹介しました。

参考

この記事を書いた人kato

加藤です。JavaScriptに興味があります。

waculでは、プログラマを募集しています。

現在はプロダクトとして、課題発見から改善提案まで自動で行うWeb改善プラットフォーム「AIアナリスト」を開発中です。

waculの採用情報へ

ページトップへ