AngularJS でネットワーク通信時にぐるぐるを出す

github に repo 作った。

https://github.com/januswel/throbberInterceptor.js


通信開始時にぐるぐるを出して通信終了時にぐるぐるを消したい。こんなときは <code>$httpProvider.interceptors</code> を使う。

https://docs.angularjs.org/api/ng/service/$http#interceptors

こういったものを用意しておく。

```js:spinner-interceptor.js
'use strict';

(function () {
    var $ = angular.element;
    var styleTemplate = 'style="width: ${width}px; height: ${height}px; top: ${top}px;"';
    var spinnerTemplate = '<div class="spinner-base" ${style}></div>';
    var backdropTemplate = '<div class="spinner-backdrop" ${style}></div>';
    var numofConnections = 0;
    var removeTargets = [];
    var show = function () {
        var style = styleTemplate
            .replace('${top}', window.pageYOffset || 0)
            .replace('${height}', window.innerHeight || 0)
            .replace('${width}', window.innerWidth || 0);
        var spinner = $(spinnerTemplate.replace('${style}', style));
        var backdrop = $(backdropTemplate.replace('${style}', style));
        var body = $(document.querySelector('body'));
        spinner.append((new Spinner()).spin().el);
        body.append(backdrop);
        body.append(spinner);
        removeTargets.push(spinner);
        removeTargets.push(backdrop);
    };
    var hide = function () {
        angular.forEach(removeTargets, function (target) {
            target.remove();
        });
        removeTargets = [];
    };

    var module = angular.module('interceptors', []);
    module.factory('spinnerInterceptor', ['$q', function ($q) {
        return {
            request: function (config) {
                if (numofConnections++ === 0) {
                    show();
                }
                return config;
            },
            response: function (response) {
                if (--numofConnections === 0) {
                    hide();
                }
                return response;
            },
            responseError: function (rejection) {
                if (--numofConnections === 0) {
                    hide();
                }
                return $q.reject(rejection);
            }
        };
    }]);
})();
```

```css:spinner.css
/*
 * spinner.css
 * */

.spinner-backdrop {
    z-index: 2000;
    position: absolute;

    background-color: black;

    opacity: .1;
}

.spinner-base {
    z-index: 2010;
    position: absolute;
}
```

spin.js を使ってる。

http://fgnass.github.io/spin.js/

で、見ての通りただの factory 。ただし、以下の 2 つの点がポイント。

1. request, response, responseError というプロパティをもっている
2. request と response は引数に渡された変数を返す関数になっている
3. responseError は引数に渡された変数で reject する関数になっている

request は通信を投げる前に呼ばれる関数、 response は通信が返ってきたあとに呼ばれる関数、 Error が末尾についてるのはエラーが発生した場合のハンドラー。もちろん requestError もある。

2, 3 番目の条件はそういう決まりで、他にも似たような interceptor があって、登録された順に実行されると考えればいい。返り値が他の interceptor に渡されるので返さなかったり他の変なもの返したりするとまともに通信できなくなる。 responseError で reject せずにそのまま return したりするとエラーが解決されたものと解釈されて success コールバックが呼ばれたりする。

あとはまぁ jqLite なのでちょっと変な書き方になってるとかだけど細かいから気にしない。


実際の使い方はこう。

```js:main.js
'use strict';

(function () {
    var app = angular.module('spinner-sample', ['interceptors']);

    // register spinner-interceptor
    app.config(['$httpProvider', function ($httpProvider) {
        $httpProvider.interceptors.push('spinnerInterceptor');
    }]);

    app.controller('http-controller', [
        '$http',
        function ($http) {
            var controller = this;
            // with spinner !!
            $http.get('items.json').success(function (data) {
                controller.data = data;
            });
        }
    ]);
})();
```

最初に <code>app.config</code> を使って <code>$httpProvider.interceptors</code> に登録さえすればあとは勝手にぐるぐるを出してくれる。