albatrosary's blog

UI/UXとエンタープライズシステム

AngularJS Advent Calendar 2014 9日目:画面遷移で行われる色々なイベントを評価してみる

ルーティングによる画面遷移でどういう順番でメソッドが呼び出されるかを確認する必要があり一度評価プログラムを書いたことがありますのでそれを。このエントリーは「AngularJS Advent Calendar 2014」12月9日の記事です。

AngularJS Advent Calendar 2014 - Adventar

画面遷移とファイルの定義

やっていることはものすごく単純でmain画面からsampleApplication画面の呼び出しを行っているだけです。AngularJSは画面遷移で多くのイベントを持っていますのでひとつひとつじっくり確認する必要があります。

f:id:albatrosary:20141210144633p:plain

サンプルコードは次の通りです:

albatrosary/preview-angular · GitHub

状態を定義するファイルは

main.js

'use strict';

angular.module('previewAngularApp')
  .config(function ($stateProvider) {
    $stateProvider
      .state('main', {
        url: '/',
        templateUrl: 'app/main/main.html',
        controller: 'MainCtrl'
      });
  });

コントローラーは次の通りです。ここで「$rootScope.$on」でイベント定義をしております。コメントをプログラム中に記載していますのでご確認ください。

f:id:albatrosary:20141210145727p:plain

main.comtroller.js

'use strict';

angular.module('previewAngularApp')
  .controller('MainCtrl', function ($scope, $rootScope, $http, $exceptionHandler) {

    $scope.awesomeThings = [];

    $scope.errormessage = $exceptionHandler;

    $http.get('/api/things').success(function(awesomeThings) {
      $scope.awesomeThings = awesomeThings;
    });
    // ページ遷移開始
    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
      console.log('$rootScope $stateChangeStart');
    });
    // ページがないとき
    $rootScope.$on('$stateNotFound', function(event, toState, toParams, fromState, fromParams){ 
      $console.log('$rootScope stateNotFound');
      event.preventDefault(); 
    });
    // 成功時
    $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
        console.log('$rootScope $stateChangeSuccess');
    });
    // エラー時
    $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams){
      console.log('$rootScope $stateChangeError');
    });
    // viewがロードされる前に発火
    $rootScope.$on('$viewContentLoading', function(event, toState, toParams, fromState, fromParams){
      console.log('$rootScope $viewContentLoading');
    });
  });

次にsampleApplication画面ですが、状態を定義するファイルは次の通りです。ここで「parent」を定義し前処理用の状態を持っています。

f:id:albatrosary:20141210150310p:plain

状態sampleApplicationを実行する前に状態hogeを実行させる

もう一つのキーワードがレゾルバ(resolve)です。レゾルバ(レゾルブ?)はビューのレンダリングが始まる前に処理を行わせたい場合に用います。

sampleApplication.js

'use strict';

angular.module('previewAngularApp')
  .config(function ($stateProvider) {
    $stateProvider
      .state('hoge',{
        template: 'hoge template:{{comments}}<div ui-view></div>',
        controller: function ($scope) {
          console.log('state.controller');
          $scope.comments = 'fuga';
        }
      })
      .state('sampleApplication', {
        parent: 'hoge',
        url: '/sampleApplication',
        templateUrl: 'app/sampleApplication/sampleApplication.html',
        controller: 'SampleapplicationCtrl',
        resolve: {
          message: function(messageService){
            return messageService.someMethod();
          },
          greeting: function(greetingService, $q, $timeout){
            return greetingService.someMethod($q, $timeout);
          },
          preprocessing: function(){
            console.log('resolve.preprocessing')
            return 'preprocessing';
          }
        },
        data: {
          customData1: 'data.customData1',
          customData2: "red"
        },
        onEnter: function(preprocessing){
          console.log('onEnter sampleApplication');
        },
        onExit: function(){
          console.log('onExit sampleApplication');
        }
      });
  });

sampleApplicationのコントローラーは次の通りです。同じく「$rootScope.$on」でイベント定義をしております。コメントをプログラム中に記載していますのでご確認ください。またファクトリーはサンプルとして幾つか定義しています。

sampleApplication.controller.js

'use strict';

angular.module('previewAngularApp')
  .controller('SampleapplicationCtrl', ['$scope','$state','sampleApplicationFactory', 'message', 'greeting', 
    function ($scope, $state, sampleApplicationFactory, message, greeting) {
      
      console.log('SampleapplicationCtrl Starting');
      console.log($state.current.data.customData1)
      console.log(message);
      console.log(sampleApplicationFactory.someMethod());
      console.log(greeting);

      // ページ遷移開始
      $scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        console.log('SampleapplicationCtrl $stateChangeStart');
      });
      // ページがないとき
      $scope.$on('$stateNotFound', function(event, toState, toParams, fromState, fromParams){ 
        console.log('SampleapplicationCtrl $stateNotFound');
        // event.preventDefault(); 
      });
      // 成功時
      $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
        console.log('SampleapplicationCtrl $stateChangeSuccess');
      });
      // エラー時
      $scope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams){
        console.log('SampleapplicationCtrl $stateChangeError');
        $scope.errormessage = "何かエラー";
      });
      // viewがロードされる前に発火
      $scope.$on('$viewContentLoading', function(event, toState, toParams, fromState, fromParams){
        console.log('SampleapplicationCtrl $viewContentLoading');
      });

    }]);

画面遷移結果

画面遷移すると次のログが得られます:

f:id:albatrosary:20141209210506p:plain

ここからわかることは url が呼び出されると

  1. リゾルバで定義したファクトリーの実行
  2. onEnterの実行
  3. parentで定義したstateの処理実行
  4. インジェクションしたファクトリーの実行
  5. コントローラーの実行
  6. 画面上の処理
  7. 次の画面のリゾルバ開始
  8. onExitの実行
  9. 次の画面のonEnterの実行

の順で処理されます。

最後に

次画面に遷移する前にエラーを補足し画面遷移させたくない場合は、リゾルバで何かしらの前処理を行うと良い結果が得られます。リゾルバでエラーが発生した場合は、呼び出されるべきページには遷移されないということになりますので。また、onEnterで画面への初期処理を行わせることができますが、onExitは注意が必要でリゾルバでエラーがあってもonExitは実行されます。onExitでキャッシュクリアした場合はリゾルバエラーで画面遷移しなかった場合に画面の状態を戻すのが厄介になるということもありますので注意が必要です。

実際に評価してからイベントとタイミングをじっくり見極めテストする必要があります。