LoginSignup
7
7

More than 5 years have passed since last update.

angular ui-routerの動的データとcssの切替え方法

Posted at

SPAの問題

SPAを本気で実装するとなるといろいろ苦しいところが出てきます。。例えば

  1. ページの初期化に動的なデータ(毎回サーバーにajaxを投げたい)が必要
  2. cssを各ページ専用のものに切り替えたい
  3. 動的にモジュールをロードしたい

今回はangularのui-routerを使用して1と2の解決方法を紹介したいと思います。

ファイル構成

├── index.html

├── angular-ui-router.js
├── angular.js
├── app.js

├── app.css
├── state1.css
├── state2a.css
├── state2b.css

├── data.json
└── state1.json

index.htmlの中身(ui-router公式サンプルを拡張)

<!DOCTYPE html">
<html lang="ja" ng-app="myApp" ng-controller="AppCtrl">

<head>
    <meta charset="utf-8">
    <title>angular ui route tips</title>
    <link rel="stylesheet" href="app.css">
    <!-- dynamic css -->
    <link rel="stylesheet" ng-repeat="path in cssPaths track by $index" ng-href="{{path}}.css">
</head>

<body>
    <a ui-sref="root">Root</a>
    <a ui-sref="state1">State 1</a>
    <a ui-sref="state2">State 2</a>
    <a ui-sref="dummy">Dummy</a>
    <div ui-view></div>

    <script src="angular.js"></script>
    <script src="angular-ui-router.js"></script>
    <script src="app.js"></script>

    <script type="text/ng-template" id="partials/root.html">
        <div ui-view></div>
    </script>

    <script type="text/ng-template" id="partials/state1.html">
        <h1>State 1</h1>
        <hr/>
        <a ui-sref="state1.list">Show List</a>
        <a ng-href="#" ng-click="increaseCount($event)">Increase count</a>
        click count : {{clickCount}}
        <div ui-view></div>
    </script>

    <script type="text/ng-template" id="partials/state2.html">
        <h1>State 2</h1>
        <hr/>
        <a ui-sref="state2.list">Show List</a>
        <a ng-href="#" ng-click="increaseCount($event)">Increase count</a>
        click count : {{clickCount}}
        <div ui-view></div>
    </script>

    <script type="text/ng-template" id="partials/state1.list.html">
        <h3>List of State 1 Items</h3>
        <ul>
            <li ng-repeat="item in items">{{ item }}</li>
        </ul>
    </script>

    <script type="text/ng-template" id="partials/state2.list.html">
        <h3>List of State 2 Things</h3>
        <ul>
            <li ng-repeat="thing in things">{{ thing }}</li>
        </ul>
    </script>

    <script type="text/ng-template" id="partials/dummy.html">
        <h3>dummy</h3>
    </script>
</body>

</html>

ステートの定義

    var myApp = angular.module( 'myApp', ['ui.router']);

    myApp.config(function( $stateProvider, $urlRouterProvider ) {

        var session, dynamicData;

        session = {
            clickCount: 0
        };

        $urlRouterProvider.otherwise( '/state1' );

        $stateProvider
            .state( 'root', {
                url: '',
                abstract: true,
                template: '<ui-view/>',
                resolve: {
                    session: function() {

                        return session;
                    },
                    dynamicData: function( $q, $http ) {

                        var defer;

                        defer = $q.defer();

                        if ( ! dynamicData ) {

                            $http({
                                method: 'GET',
                                url: 'data.json'
                            }).then(function successCallback( response ) {

                                dynamicData = response.data;

                                defer.resolve( dynamicData );
                            });
                        } else {

                            defer.resolve( dynamicData );
                        }

                        return defer.promise;
                    }
                }
            })
            .state( 'state1', {
                parent: 'root',
                url: '/state1',
                templateUrl: 'partials/state1.html',
                css: 'state1',
                resolve: {
                    myData: function( $q, $http ) {

                        var defer;

                        defer = $q.defer();

                        $http({
                            method: 'GET',
                            url: 'state1.json'
                        }).then(function successCallback( response ) {

                            defer.resolve( response.data );
                        });

                        return defer.promise;
                    }
                },
                controller: function( $scope, $state, session, dynamicData, myData ) {

                    $scope.clickCount = session.clickCount;

                    $scope.increaseCount = function( $event ) {

                        $event.preventDefault();

                        $scope.clickCount = session.clickCount = session.clickCount + 1;
                    };
                }
            })
            .state( 'state1.list', {
                url: '/list',
                templateUrl: 'partials/state1.list.html',
                controller: function( $scope ) {

                    $scope.items = ['A', 'List', 'Of', 'Items'];
                }
            })
            .state( 'state2', {
                parent: 'root',
                url: '/state2',
                css: ['state2a', 'state2b'],
                templateUrl: 'partials/state2.html',
                controller: function( $scope, $state, session, dynamicData ) {

                    $scope.clickCount = session.clickCount;

                    $scope.increaseCount = function( $event ) {

                        $event.preventDefault();

                        $scope.clickCount = session.clickCount = session.clickCount + 1;
                    };
                }
            })
            .state( 'state2.list', {
                url: '/list',
                templateUrl: 'partials/state2.list.html',
                controller: function( $scope ) {

                    $scope.things = ['A', 'Set', 'Of', 'Things'];
                }
            })
            .state( 'dummy', {
                url: '/dummy',
                templateUrl: 'partials/dummy.html'
            });
    });

ステート一覧

  1. abstractステートのroot
  2. rootの子としてstate1
  3. state1の子としてstate1.list
  4. rootの子としてstate2
  5. state2の子としてstate2.list
  6. 最後に独立したdummyステート

abstractステートとは

  • ステートとして存在するがアクティベートできない(遷移させられない、Rootのリンクをクリックするとエラーになるのがわかる)
  • 子ステートへのプレフィックスとなるurlを指定できる
  • resolveプロパティーで子ステートの依存を解決できる
  • dataプロパティーで子ステートへデータを渡せる

今回必要な一部のみ説明しますが、別の便利なプロパティーもまだあります。

ページの初期化に動的なデータを取得の解決方法

シナリオ

  1. 各ステートで共通のデータが必要でそれをrootステートで用意してあげます。
  2. 各ステートが独自に必要なデータはそれぞれがresolveで取得します。
  3. 子ステート間のセッションとしてsessionプロパティーを提供します。

ルートのresolveプロパティーでの動作

rootステートのresolvedynamicDataプロパティーでdata.json取得のプロミスを返しています。resolveはプロミスが返されるとそれが解決されるまで子ステートをアクティベートしません。

子ステートから親のデータを参照

子ステートのコントローラーは親resolveのプロパティー名をそのままDIできるようになっています。

resolveの実行タイミング

resolveが実行されるのは別のステートから遷移して来た場合のみです。すなわち同じ親を持つ兄弟間での遷移には実行されません。上記の例ではDummyからState1State2へ遷移した場合のみ実行されます。

データのキャッシュ

今回はajaxの結果をキャッシュするためにdynamicDataのローカル変数に代入してから返しています。この場合dynamicDataは共有されるので子ステートで更新する際に要注意です。

個別resolve

各ステートで必要なデータは各自のresolveで取得します。今回はstate1.jsonstate1がアクティベートされる度に取得してます。

オブジェクトの共有

セッションは各ステートでIncrease Countリンクで更新され、別の兄弟ステートへ共有されているのがわかります。共有されたくないデータの場合はdataプロパティーを使用データ実現可能です。(詳細

cssを各ページ専用のものに切り替えたい場合の解決方法

  1. ステートの定義時にcssプロパティーを追加する
  2. cssプロパティーが設定されている場合は自動的にlinkタグを作成する
  3. linkタグの作成タイミングはステートのイベント内で行う為各ステートでの処理は不要

コントローラー

    myApp.controller( 'AppCtrl', ['$rootScope', '$scope', function( $rootScope, $scope ) {

        $scope.cssPaths = [];

        $rootScope.$on( '$stateChangeStart',
            function( event, toState, toParams, fromState, fromParams ) {

                $scope.cssPaths = [];

                if ( toState.css ) {

                    $scope.cssPaths = angular.isArray( toState.css ) ? toState.css : [toState.css];
                }
            })
    }]);

動的css用linkタグの作成

<link rel="stylesheet" ng-repeat="path in cssPaths track by $index" ng-href="{{path}}.css">

cssの中身

/* state1.css */
body {
    background-color: yellow;
}
body * {
    color: red;
}

/* state2a.css */
body {
    background-color: green;
}
/* state2b.css */
body * {
    color: white;
}

今回は複数cssを対応するため、配列の指定も可能にしてあります。
cssフォルダーを別に分けている場合はlinkタグのテンプレートの部分を編集することで対応可能です。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7