Humanity

Edit the world by your favorite way

Angular UI Router の resolve プロパティが失敗(reject)した場合に値を受け取りたい

angularjs - Reloading current state - refresh data - Stack Overflow

Update for newer versions:

$state.reload();

Which is an alias for:

$state.transitionTo($state.current, $stateParams, { 
  reload: true, inherit: false, notify: true
});

まず上記エントリで $state.reload()$state.transitionTo() を呼び出すらしいことが分かった。 $state.go() も同様に $state.transitionTo() を呼び出す。

Home · angular-ui/ui-router Wiki · GitHub

$rootScope.$on('$stateChangeStart', 
function(event, toState, toParams, fromState, fromParams, options){ 
    event.preventDefault(); 
    // transitionTo() promise will be rejected with 
    // a 'transition prevented' error
})

そしてどうやら $state.transitionTo() は promise を返すらしい。 つまり resolve プロパティで reject したら $state.reload()$state.go() が返す promise で受け取れる。

  angular.module('sample')
    .config(['$stateProvider', function($stateProvider) {
      $stateProvider
        .state('top', {
          url: '/',
          templateUrl: '/page/top.html',
          controller: 'topCtrl as $ctrl',
          resolve: {
            fetchResult: ['Book', '$q', function (Book, $q) {
              var deferred = $q.defer()
              Book.get().$promise.then(function onSuccess(res) {
                deferred.resolve({
                  books: res.books,
                  alerts: []
                })
              }, function onError(res) {
                if (angular.isObject(res) &&
                    angular.isString(res.statusText) &&
                    angular.isObject(res.data) &&
                    angular.isObject(res.data.error) &&
                    angular.isString(res.data.error.msg)) {
                  deferred.resolve({
                    books: [],
                    alerts: [{
                      type: 'danger',
                      msg: res.statusText + ' (' + res.data.error.msg.trim() + ')'
                    }]
                  })
                } else {
                  // Offline?
                  deferred.reject({
                    books: [],
                    alerts: [{
                      type: 'danger',
                      msg: 'Couldn\'t connect to server.'
                    }]
                  })
                }
              })
              return deferred.promise
            }]
          }
        })
    }])

    .controller('topCtrl', ['fetchResult', '$state', function topCtrl(fetchResult, $state) {
      var $ctrl = this
      $ctrl.books = fetchResult.books
      $ctrl.alerts = fetchResult.alerts

      $ctrl.closeAlert = function closeAlert(index) {
        $ctrl.alerts.splice(index, 1)
      }

      $ctrl.refresh = function refresh() {
        $state.reload().catch(function onError(fetchResult) {
          $ctrl.alerts = $ctrl.alerts.concat(fetchResult.alerts)
        })
      }
    }])

ほぼ実際のコードからの引用なので長くなってしまった (日記ブログからの転載まんまなので雑)。 画面上の更新ボタンを押した時($ctrl.refresh())、fetchResult を取得するために投げている GET リクエストが失敗し、オフラインっぽかったらその旨を画面上に表示する。

UI Bootstrap のウインドウを Draggable にする

これでできた。

angular.js を読み込む前に jQuery, jQuery UI の .js を読み込む必要があります。

  angular.module('app')
    .directive('uibModalWindow', function () {
      return {
        restrict: 'A',
        link: function (scope, element) {
          $(element).draggable()
        }
      }
    })

情報源は以下の Stack Overflow のエントリ。 ただ注意点として、UI Bootstrap の全ディレクティブに uib- が付くようになったので、コメントの modalWindowuibModalWindow と読み替える必要がある。

angularjs - AngularUI modal to be draggable and resizable - Stack Overflow

こういう記事もあったけど、directive 以外で element をいじるのは Angular 的にダメなんじゃないだろうか。

angular.js/ui-bootstrapでui-bootstrapのモーダルウィンドウをドラッガブルにするには - わからん

あとこういう記事もあった。

Twitter Bootstrap のモーダルをドラッグで動かしたいときは - AngularJS Ninja Blog

このままだとセレクタ.modal.fadeと.modal.fade.inに定義されているtopとtransitionのプロパティ(モーダルが上から滑り降りてくるトランジション)による影響で、縦方向のドラッグがの〜んびりした動きになって違和感が残る。 なので、上から滑り降りてくるトランジションはあきらめて、class 属性からfadeを消してしまうのがいいと思う。

この現象は確認できなかった。 自分の確認不足の可能性もあるので困った時のためにメモ。

ところで:$uibModal の resolve プロパティ

$uibModal のドキュメント見てたらふと resolve ってプロパティを見つけた。 これにキーと任意のオブジェクトを渡せば、そのキー名でモーダルウィンドウ側のコントローラーで inject できるようになるらしい。 こんな感じ (コードはここからの引用)。

$uibModal.open({
    controller : 'DialogController',
    resolve: {
        params: function() {
            return {
              title:'DialogSample',
              message:'Take it easy!'
            };
        }
    }});

angular.module('DialogSample').controller('DialogController', ['$uibModalInstance', 'params',
      function($uibModalInstance, params){
        console.log(params.title); // => 'DialogSample'
        console.log(params.message); // => 'Take it easy!'
      }]);

わざわざ受け渡しをするだけのサービスを作る必要はなかった…すごく無駄な再発明をしててすごくショックを受けたので羞恥周知してみる。

Chrome DevTools で使える通信制限(オフライン環境をエミュレートする方法)

先日、単体テストを行っていて JS からのリクエストを失敗させるテストケースがあった。 バックエンドのサーバを停止させればいいが、毎回起動したり停止したりするのは面倒。 ということで効率の良いテスト実施方法がないか調べていた。*1

通信制

結論から言うと、タイトルの通り Chrome の DevTools の機能で擬似的にオフラインの環境や、回線が細い環境をエミュレートできる。

以下スクリーンショット

オフライン環境をエミュレート

まずは Ajax で GET リクエストを飛ばす。 コンソールで jQuery を読み込ませて GET / してみる。

// jQuery をロード
var $script = document.createElement('script');
$script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild($script);
// GET リクエスト送信
jQuery.get('/', res => { window.alert(res) })

f:id:tyru:20160808004231p:plain

そして制限をかける方法と、かけた結果がこちら。

f:id:tyru:20160808004235g:plain f:id:tyru:20160808004239p:plain

回線の細い環境をエミュレート

2枚目の GIF にもチラッと映ってる通り、DevTools の Network タブをクリックし、 「No throttling」のプルダウンをクリックすると「Presets」以下に回線が細い環境などをエミュレートするためのプリセットが色々表示されている。

Chrome DevTools は偉大

JS を実行制限させたかったら大体 DevTools でなんとかできる(適当)と思ってるので、もしそういうケースがあったら一度 DevTools 開いて色々探してみると良い。

あと GyazoGyazo GIF は便利。

*1:そもそも自動化しろという話だけど…