AngularJS
AngularUI
ui-router

AngularUI Router Wiki - Home.md の日本語訳

More than 1 year has passed since last update.

https://github.com/angular-ui/ui-router/wiki のおおざっぱな訳と公式の記事に少しだけサンプルを追加しました。

記事内のリンク先は、直接元コンテンツの URL に飛びます。(いずれ訳したい)


このガイドは UI-Router のコンポーネントとそのオプションへ深く関わるための記事です。もし、クイックリファレンスガイドを見たい場合は API Reference を参照してください。

State Manager

angular-ui router の提供する $stateProvider は AngularJS v1 のルータを元に、純粋な状態(ステート)へ注力して動作します。

  • 状態は、全体的なUIとナビゲーションの観点から、アプリケーション内の「場所」に対応しています。
  • 状態は、(コントローラ/テンプレート/ビューのプロパティを介して)UIがその場所でどのように見え、動作するかを記述します。
  • 状態は、多くの場合に共通点を持っており、このようなモデルでこれらの共通点がもたらす物は、状態の階層、すなわち親/子の状態、ネストされた状態である。(翻訳自信なし)

一番シンプルな書き方

<body ng-controller="MainCtrl">
    <section ui-view></section>
</body>
angular.module('sample', ['ui.router']).config([$stateProvider, function($stateProvider) {
    $stateProvider.state('contacts', {
        template: '<h1>My Contacts</h1>'    
    }); 
}]);
テンプレートはどこに挿入されるのか

state が作動した場合、親の state のテンプレート内の ui-view アトリビュートが設定されたエレメントに挿入される。上記のように親のテンプレートが存在しない場合、 index.html が親のテンプレートとして扱われる。

state を作動させる

上記のように contacts の state を作動させるためには、以下の方法がある。

  1. $state.get() メソッドを呼ぶ

    ex. $state.get('contacts')

    詳細

  2. ui-sref ディレクティブを付けた要素をクリックする (ex)

    ex. <a ui-sref="contacts">Go contacts</a>

    詳細

  3. URL を用いて、 state にアクセスする

    ex. http://example.com/#/contacts

    詳細

Template について

テンプレートを設定する方法はいくつかある。

一番シンプルな方法は template プロパティに String として指定する。

$stateProvider.state('contacts', {
    template: '<h1>My Contacts</h1>'   
});

大体は、以下のように分割された html ファイルへのパスを templateUrl プロパティへ指定する。

$stateProvider.state('contacts', {
    templateUrl: 'contacts.html'    
});

templateUrl プロパティは、関数としても指定できる。その際、関数に stateParams という引数を取ることができる。(これはインジェクトされた引数ではない)

$stateProvider.state('contacts', {
    templateUrl: function(stateParams) {
        return 'partials/contacts.' + stateParams.filterBy + '.html';
    }
});

もしくは、インジェクトすることができる templateProvider プロパティを使って以下のようにテンプレートを返すことができる。

$stateProvider.state('contacts', {
    templateProvider: function($timeout, $stateParams) {
        return $timeout(function() {
            return '<h1>' + $stateParams.contactId + '</h1>';
        }, 100); 
    }
});

ui-view ディレクティブ内にはデフォルトのコンテンツを持つことができ、 state が作動した場合に置換される。この場合、 state の管理外になったときはデフォルトのコンテンツが再び挿入される。

<body>
    <ui-view>
        <i>ここのコンテンツが表示される</i>
    </ui-view>
</body>

Controller について

テンプレートには1つのコントローラを指定できる 注意 指定されたコントローラーは、テンプレートが指定されていない場合にはインスタンス化されない。

コントローラは以下のように controller プロパティへ指定する。

$stateProvider.state('contacts', {
    template: '<h1>{{title}}</h1>',
    controller: function($scope){
        $scope.title = 'My Contacts';
    }
})

また、モジュール内に定義済みのコントローラ名を指定することもできる。

$stateProvider.state('contacts', {
    template: '<h1>{{title}}</h1>',
    controller: 'ContactsController'
})

また、コントローラ名は Controller As Syntax でも指定することができる。

$stateProvider.state('contacts', {
    template: '<h1>{{contact.title}}</h1>',
    controller: 'ContactsCtrl as contact'
})

さらに進んだ使い方をする場合、controllerProvider を用いて動的にコントローラの関数や名前を指定することができる。

$stateProvider.state('contacts', {
    template: ...,
    controllerProvider: function($stateParams) {
        var ctrlName = $stateParams.type + "Controller";

        return ctrlName;
    }
})

コントローラ内では $scope.on() を用いることで state の遷移をイベントを監視することができる。

コントローラは必要になった場合にインスタンス化され、スコープが生成される。すなわち、ユーザが URL を用いて state を作動させた場合、 $stateProvider は必要なテンプレートをビューにロードし、テンプレートのスコープにコントローラを紐付ける。

Resolve について

コントローラと一緒にコンテンツや state のためにカスタマイズしたデータを準備する場合は resolve プロパティを使用する。

resolve プロパティは、コントローラにインジェクトするべきものの依存関係マップを指定するオプションである。

プロミスで扱ういくつかの依存関係は、コントローラがインスタンス化される前、また $routeChangeSuccess イベントが発火する前にその解決や変換が行われる。

resolve プロパティは、オブジェクトでマッピングしていく。そのマッピングオブジェクトは、以下のような key/value のペアで指定する。

  • key - {String}: コントローラにインジェクトされる名前を指定
  • factory - {String|Function}:
    • String で指定した場合、サービスへのエイリアスとして扱われる
    • Function で指定した場合、その関数はインジェクトされて戻り値が依存した値となる。戻り値がプロミスだった場合、それはコントローラがインスタンス化される前に解決され、コントローラにインジェクトされる

resolve 内のオブジェクトは、コントローラがインスタンス化される前に必ず解決されなければならない。(プロミスの場合は deferred.resolve() を通っていること)

resolve がどのようなパラメータとしてコントローラへインジェクトされるかを注意すること。

$stateProvider.state('myState', {
    resolve:{
        // 関数を使って値を返す一番簡単な例
        // プロミスを使っていないので、即座に値が解決される
        simpleObj:  function(){
            return { value: 'simple!' };
        },

        // 関数を使ってプロミスを返す例
        // resolve が使われる典型的なケースとして、$http を用いてサービスをインジェクトしたい場合を例とする
        promiseObj:  function($http){
           // $http は url に対するデータのためのプロミスを返す
           return $http({method: 'GET', url: '/someUrl'});
        },

        // プロミスを使った他の典型的な例として
        // 結果のデータを .then メソッドを用いて加工したい場合は以下のようになる
        promiseObj2:  function($http){
           return $http({method: 'GET', url: '/someUrl'})
              .then (function (data) {
                  return doSomething(data);
              });
        },        

        // String でサービスの名前を指定する例
        // 以下では、モジュール内の 'translations' というサービスを返す
        // Note. サービスがプロミスを返した場合は、上記の関数でプロミスを返す場合と同様に動作する
        translations: "translations",

        // Example showing injection of service into
        // resolve function. Service then returns a
        // promise. Tip: Inject $stateParams to get
        // access to url parameters.
        // 関数へインジェクションを行い、サービスを使う例
        // 例ではサービスはプロミスを返している
        // Tip. URL のパラメータにアクセスするために $stateParams をインジェクトすることができる
        translations2: function(translations, $stateParams){
            // getLang メソッドは、$http を用いて "/:lang/home" のような URL をリクエストするような状況を想定している
            return translations.getLang($stateParams.lang);
        },

        // 自分自身で生成したプロミスを使用する例
        greeting: function($q, $timeout){
            var deferred = $q.defer();
            $timeout(function() {
                deferred.resolve('Hello!');
            }, 1000);
            return deferred.promise;
        }
    },

    // コントローラは resolve に指定されたものの完了を待ってからインスタンス化される
    // 例としてあげるなら、コントローラは promiseObj のプロミスが完了されるまでインスタンス化されない、ということ
    // 完了した段階で、 resolve に指定した物はコントローラにインジェクトされ使用することができる
    controller: function($scope, simpleObj, promiseObj, promiseObj2, translations, translations2, greeting){
        $scope.simple = simpleObj.value;

        // promiseObj は使用可能になっている
        $scope.items = promiseObj.items;
        $scope.items = promiseObj2.items;

        $scope.title = translations.getLang("english").title;
        $scope.title = translations2.title;

        $scope.greeting = greeting;
    }
});

子のステートで使用する場合の resolve などの詳細はここ

state にカスタムデータを渡す

state を設定するオブジェクトには、データを渡すことができる。

※渡す場合は他のプロパティとのコンフリクトを避けるため data プロパティでの設定をおすすめする

// 例では、オブジェクトを渡して state を設定する方法と string で指定する方法を記す
var contacts = { 
    name: 'contacts',
    templateUrl: 'contacts.html',
    data: {
        customData1: 5,
        customData2: "blue"
    }  
};

$stateProvider
    .state(contacts)
    .state('contacts.list', {
        templateUrl: 'contacts.list.html',
        data: {
            customData1: 44,
            customData2: "red"
        } 
    });

上記のように state を設定した場合、コントローラからは以下のようにアクセスすることができる

function Ctrl($state){
    console.log($state.current.data.customData1) // outputs 5;
    console.log($state.current.data.customData2) // outputs "blue";
}

子のステートで使用する場合の data プロパティなどの詳細はここ

onEnter, onExit コールバックについて

state にはオプションとして onEnter onExit のプロパティを用いて、それぞれ state のアクティブ、非アクティブのタイミングでコールバックを呼ぶことができる。コールバックは、resolve で設定した依存物にアクセスすることができる。

$stateProvider.state("contacts", {
    template: '<h1>{{title}}</h1>',
    resolve: { title: 'My Contacts' },
    controller: function($scope, title){
        $scope.title = 'My Contacts';
    },
    onEnter: function(title){
        if(title){ ... do something ... }
    },
    onExit: function(title){
        if(title){ ... do something ... }
    }
});

State Change Event について

これらのイベントは全て $rootScope で発火する

  • $stateChangeStart - 遷移が始まったときに発火する
$rootScope.$on('$stateChangeStart',
               function(event, toState, toParams, fromState, fromParams){ ... });

Note: event.preventDefault() を使うと何らかの出来事のために遷移を止めることができる

$rootScope.$on('$stateChangeStart', 
               function(event, toState, toParams, fromState, fromParams){ 
                   event.preventDefault(); 
                   // transitionTo() のプロミスは 'transition prevented' エラーを持ってリジェクトされる
               });
  • $stateNotFound - v0.3.0 - 遷移するべき state が存在しない場合に発火する。このイベントは任意のハンドラが(見つからない state を遅延ロードすることによって)エラーに対処するための唯一の方法である。ハンドラに渡される unfoundState オブジェクトは、下記のサンプルにて参照のこと。event.preventDefault() を使うことによって、遷移を中断させることができる(その場合に transitionTo() プロミスは 'transition abort' イベントをもってリジェクトされる) その他、遅延ロードによる state のロードなどはこちらを参照のこと。
// lazy.state が定義されていないとする
$state.go("lazy.state", {a:1, b:2}, {inherit:false});

// ハンドラの処理
$rootScope.$on('$stateNotFound', 
               function(event, unfoundState, fromState, fromParams){ 
                   console.log(unfoundState.to); // "lazy.state"
                   console.log(unfoundState.toParams); // {a:1, b:2}
                   console.log(unfoundState.options); // {inherit:false} + default options
               });
  • $stateChangeSuccess - 遷移が完了した際に発火する
$rootScope.$on('$stateChangeSuccess', 
               function(event, toState, toParams, fromState, fromParams){ ... })
  • $stateChangeError - 遷移の際に何らかのエラーが発生した場合に発火する。 注意 resolve 内の関数で発生した様々なエラーは伝統に従って throw されないので、必ず $stateChangeError のハンドラで catch しなければならない。
$rootScope.$on('$stateChangeError', 
               function(event, toState, toParams, fromState, fromParams, error){ ... })

View Load Event について

  • $viewContentLoading - ビューがローディングされ、DOM がレンダリングされる前に発火される。このイベントは $scope に対して発火する。
$scope.$on('$viewContentLoading', 
           function(event, viewConfig){ 
               // ここではビューの全てのコンフィグプロパティにアクセスすることができる
               // また 'targetView' プロパティという特別なものが提供される
               // viewConfig.targetView 
           });
  • $viewContentLoaded - ビューがローディングされ、DOM がレンダリングされたあとに発火する。このイベントは $scope に対して発火する。
$scope.$on('$viewContentLoaded', 
           function(event){ ... });

おかしい内容があればコメントか damele0n@twitter まで。