アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

AngularJSに触れてみる その2




前回のブログに引き続き、AngularJSの機能を触っていきたいと思います。 こちらは今回機能紹介のために用意したPC版のデモです。モバイルでは見にくいので、でうまく動かない場合はPCに最新版のブラウザ (最新版のGoogle Chromeなど) をダウンロードしてからご覧ください。



今回はデモで用いられているAngularJSの機能、主にngViewについて紹介していきたいと思います。ngViewを用いることでURLの切り替えによって読み込むコンテンツを切り替えることができます。

上記デモはボタンをクリックするごとにURLを切り替えて、そのURLに対応したHTMLテンプレートとコントローラーを読み込ませて表示を行なっています。また、切り替える時のアニメーションにはngAnimateというAngularJSのアニメーション機能を使っています。

まずはindex.htmlソースコードを掲載します [※ソースは紹介に必要のない部分は削除しています] 。なお、画像を除いたソースコードは最下部に置いてあります。

index.html






<!DOCTYPE html>
<html ng-app="MonaApp">
  <head>
    <meta name="viewport" content="width=device-width; initial-scale=1.0;">
    <base href="/blog1017/" />
    <link rel="stylesheet" href="css/style.css"/>
    <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
    <script src="http://code.angularjs.org/angular-1.2.0-8336b3a/angular.min.js"></script>
    <script src="http://code.angularjs.org/1.2.0-rc.2/angular-animate.min.js"></script>
    <script src="http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js"></script>   
    <script src="js/app.js"></script>
  </head>
  <body>

     <div id="header">
        <p id="title">AngularJS Books</p>
    </div>

    <div id="container" ng-controller="MonaCtrl">
        <div  ng-view class="view-demo"></div>
        <div id="sidebar">
	  <ul>
              <li id="book1"><a href="book1">Book1</a></li>
              <li id="book2"><a href="book2">Book2</a></li>
              <li id="book3"><a href="book3">Book3</a></li>
              <li id="book4"><a href="book4">Book4</a></li>
              <li id="book5"><a href="book5">Book5</a></li>
         </ul>
        </div>
     </div>  
  </body>
</html>


app.js





var app = angular.module('MonaApp', ['ngRoute', 'ngAnimate']);

app.config(function($routeProvider, $locationProvider) {

  $routeProvider.when('/book1', {
    templateUrl: 'templates/book.html',
    controller:book1Ctrl
  });

  $routeProvider.when('/book2', {
    templateUrl: 'templates/book.html',
    controller:book2Ctrl
  });


  $routeProvider.when('/book3', {
    templateUrl: 'templates/book.html',
    controller: book3Ctrl
  });
  });
});


app.config(function($locationProvider) {
      $locationProvider.html5Mode(true);
});


function MonaCtrl($location, $scope) {

    var pager = 1;

    setInterval(function() {
      $scope.$apply(function(){
        if(pager === 6){
          pager = 1;
        }

        $location.path('/book'+pager);
        pager++;
      });
    }, 5000); 
  }
}

function book1Ctrl($scope) {

  angular.element("#sidebar ul li").css("background", "#24100F");
  angular.element("#book1").css("background", "#D0B29A");

  $scope.MonaItems = [];

  $scope.MonaItems.push({
         "title"  : "Mastering Web Application Development with AngularJS",
         "author" : "Author: Pawel Kozlowski, Peter Bacon Darwin",
         "image"  :  "images/1.png" ,
         "link"   : "http://www.amazon.co.jp/Mastering-Application-Development-AngularJS-ebook/dp/B00EQ67J30/ref=sr_1_4?ie=UTF8 &qid=1381665653 &sr=8-4 &keywords=AngularJS" 
  });
}


function book2Ctrl($route, $routeParams, $location, $scope) {

  angular.element("#sidebar ul li").css("background", "#24100F");
  angular.element("#book2").css("background", "#D0B29A");

  $scope.MonaItems = [];

  $scope.MonaItems.push({
         "title"  : "AngularJS",
         "author" : "Author: Brad Green, Shyam Seshadri",
         "image"  :  "images/2.png" ,
         "link"   : "http://www.amazon.co.jp/AngularJS-Brad-Green/dp/1449344852/ref=tmm_pap_title_0?ie=UTF8 &qid=1381668266 &sr=1-1"  
  });
}


function book3Ctrl($route, $routeParams, $location, $scope) {


  angular.element("#sidebar ul li").css("background", "#24100F");
  angular.element("#book3").css("background", "#D0B29A");

$scope.MonaItems = [];

  $scope.MonaItems.push({
         "title"  : "Angularjs in Action",
         "author" : "Author: Brian Ford, Lukas Ruebbelke ",
         "image"  :  "images/3.png" ,
         "link"   : "http://www.amazon.co.jp/Angularjs-Action-Brian-Ford/dp/1617291331/ref=sr_1_6?ie=UTF8 &qid=1381731976 &sr=8-6 &keywords=AngularJS"
  });
}


フォルダ構成は下記になります。




ではngViewの紹介に移っていきます。

ngView




ngViewを使うことでindex.htmlに読み込むHTMLテンプレートおよびコントローラー等をURLによって切り替えることができます。URLが切り替わるたびにそのURLに紐づけられたHTMLテンプレートとコントローラーに切り替わります。

上記デモでテンプレートが切り替わっている場所はindex.htmlに定義してある下記の場所です。divタグに属性としてng-viewが記述されていることが確認できると思います。



<div ng-view class="view-demo"></div>


具体的にapp.jsのコードを見ていきましょう。



var app = angular.module('MonaApp', ['ngRoute', 'ngAnimate']);


AngularJSではモジュールという機能があります。モジュールという機能によってアプリが起動 (ブート) する際の設定や挙動などを定義することができます。詳細は本家ドキュメントをご覧ください。


ここでは MonaAppという自分で作成したモジュールに、ngRoute (ngViewのルーティングのために必要) およびngAnimate (ngAnimateというAngularJSのアニメーション機能を用いるためにver1.2から必要 ) というモジュールを読み込んでいます。

このモジュールを読み込むためには、以下のJSファイルを読み込む必要があります。これらのファイルはindex.htmlでそれぞれロードしています。



<script src="http://code.angularjs.org/angular-1.2.0-8336b3a/angular.min.js"></script>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular-animate.min.js"></script>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js"></script>  


また、ngRouteを使う場合にはindex.htmlのタグ中のタグで設定しておきます。私の場合はblog1017という名前のフォルダー直下にベースURLを設定してあるので以下のようにindex.htmlのタグに記述しています。




    <base href="/blog1017/" />




下記ではngRouteロードされた$routeProviderの設定を行っています。$routeProviderはangular-route.jsで読み込まれるサービスであり、ルートプロバイディングの設定を行います。



app.config(function($routeProvider) {

  $routeProvider.when('/book1', {
    templateUrl: 'templates/book.html',
    controller:book1Ctrl
  });

  $routeProvider.when('/book2', {
    templateUrl: 'templates/book.html',
    controller:book2Ctrl
  });

   .....

});


$routeProvider.whenでテンプレートとコントローラーの設定を行います。今回は全て同じtemplates/book.htmlというテンプレートを使い回しています。



  $routeProvider.when('/book1', {
    templateUrl: 'templates/book.html',
    controller:book1Ctrl
  });


上記の記述は


1:ベースURL/book1にURLが切り替わった時に




<div  ng-view class="view-demo"></div>

2:上記に対してtemplates/book.htmlというHTMLテンプレートを読み込ませて


3:book1Ctrlというコントローラーを割り当てる


という設定を行っています。


PCでこのブログをご覧の方は実際にこちらのデモで試してみましょう。うまく動かない場合には最新のGoogle Chromeなどをダウンロードしてお試しください。



上記のURLは[http://s3.asial.co.jp/~ataru/blog1017/index.html]ですが、book1ボタンを押した際に、aタグでURLを変更しています。その際に

1:templates/book.htmlというテンプレートが読み込まれ


2:book1Ctrlというコントローラーが割り当てられます





そうすれば、[http://s3.asial.co.jp/~ataru/blog1017/book1]にURLが切り替わった時にng-view属性を指定した箇所に読み込まれるテンプレートとコントローラーが切り替わります。


割り当てられたbook1Ctrlコントローラーを見てみましょう。




function book1Ctrl($scope) {

  angular.element("#sidebar ul li").css("background", "#24100F");
  angular.element("#book1").css("background", "#D0B29A");

  $scope.MonaItems = [];

  $scope.MonaItems.push({
         "title"  : "Mastering Web Application Development with AngularJS",
         "author" : "Author: Pawel Kozlowski, Peter Bacon Darwin",
         "image"  :  "images/1.png" ,
         "link"   : "http://www.amazon.co.jp/Mastering-Application-Development-AngularJS-ebook/dp/B00EQ67J30/ref=sr_1_4?ie=UTF8 &qid=1381665653 &sr=8-4 &keywords=AngularJS" 
  });
}



MonaItemsという配列の中に本のタイトルや著者名、画像、リンクなどのオブジェクトを入れています。これが下記のテンプレートであるtemplates/book.html中に記述されたngRepeatの中でitemとして展開されます。それをtemplates/book.htmlの中で、{{item.title}}や{{item.author}}、{{item.link}}という形でAngularJSのExpressionsで記述しています。Expressionsは前回のブログで紹介しています。

template/book.html





<div class="contents" ng-repeat = "item in MonaItems" >
    <h1>{{item.title}}</h1>
    <h2>{{item.author}}</h2>
 
    <a href={{item.link}} target=”_blank”><img src={{item.image}} /></a>
    <a href={{item.link}} target=”_blank”>Purchase this book on Amazon</a>
</div>


今回はテンプレートとして用いているのはtemplates/book.html1つですが、book1Ctrlbook2CtrlなどバインドされたコントローラーごとにMonaItemsという配列に代入される値は異なるので、URLが切り替わるたびに、コンテンツの異なるtemplates/book.htmlが表示されます。



ngAnimate



ngAnimateはAngularJSが提供しているアニメーションのための機能です。上記デモでもngAnimateを使ってアニメーションを行なっています。

ngAnimateの仕組みは1.1.5から1.2にAngularJSがアップグレードする際に仕組みが変わっています。現在AngularJSのStable版の最新は1.0.8 (2013/10/17現在) なので、まだまだngAnimateの仕様は今後変更される可能性があります。

詳細はyear of mooというこちらブログが詳しいので興味のある方はご覧ください。

Animation in AngularJS 1.2

ngAnimateの仕組みについて紹介します。ngAnimateをサポートするディレクティブについては本家ドキュメントのngAnimateの項目をご覧ください。ngRepeatやngView、ngIncludeなどがサポートされています。

上記デモでngAnimateでアニメーションしている箇所は以下です。ここではclassとして指定されているview-demoに着目してください。



<div ng-view class="view-demo"></div>


ngAnimateではアニメーションのイベントが発火した際にCSSのクラスが指定要素に追加される形でアニメーションを行ないます。幾つか使い方があるのですが、ngAnimateを使用したい場合には、例えばngRepeat、ngView、ngIncludeなどngAnimateをサポートしているディレクティブにクラスを設定します。ここではview-demoと設定しています。そして、そのview-demoに関連して、ng-enterng-leaveなどの幾つかのクラスのCSSを記述します。以下のCSSを見てみましょう。.view-demoに.ng-enter、.ng-enter-active、.ng-leave,.ng-leave-asctiveなどのクラスを記述しています。


ngViewではHTMLテンプレートや何らかのコンテンツが読み込まれて表示された時にenterイベントが、コンテンツが表示されなくなった時にleaveイベントが発火します。enterイベントが発火した時には.ng-enterと.ng-enter-activeクラスが対象の要素に対して追加されます。leaveイベントが発火した際には、ng-leave、ng-leave-activeクラスが対象の要素に追加されます。アニメーションが完了するとこれらの付加されたCSSは取り除かれます。

例えば、URLが切り替わって[ book.html & book1Ctrl ]という組み合わせのコンテンツが消え、[ book.html & book2Ctrl ]という組み合わせのコンテンツが新しく表示されるとき、ng-enterおよびng-enter-activeが新しく表示される[ book.html & book2Ctrl ]に適用され、ng-leaveおよびng-leave-activeが表示されなくなる[ book.html & book1Ctrl ]に適用されます。アニメーションが完了した時にはこれらのクラスは取り除かれます。




style.css





/*対象の属性 (view-demo) を持つ要素に追加されるCSS*/
	.view-demo.ng-enter, .view-demo.ng-leave {
  		-webkit-transition-duration: 1s;
  		-moz-transition: 1s;
  		-o-transition: 1s;
  		transition:1s
     display:block;
  		position:absolute;
	}
 /*コンテンツが表示される時に対象の要素に追加されるCSS(始点)*/
	.view-demo.ng-enter {
  		left:100%;
	}
 /*コンテンツが表示される時に対象の要素に追加されるCSS*/
	.view-demo.ng-enter-active {
  		left:0%;
	}
 /*コンテンツが表示されなくなる時に対象の要素に追加されるCSS(始点)*/ 
	.view-demo.ng-leave { 
		opacity: 1;
  		left:0%;
	}
 /*コンテンツが表示されなくなる時に対象の要素に追加されるCSS*/ 
	.view-demo.ng-leave-active {
  		opacity: 0;
  		left:100%;
	}



このCSSを定義することで実際にURLが変わってテンプレートが切り替わった時には

1:新しく表示されるコンテンツのアニメーションの始点 (最初の状態) として


	.view-demo.ng-enter {
  		left:100%;
	}

が適用されます。

2:そして次に、新しく表示されるコンテンツに対して



	.view-demo.ng-enter-active {
  		left:0%;
	}


が適用されます。結果として、新しく入ってくるコンテンツはleftが100%の状態からleftが0の状態になるので、表示されるときには右から左へとスライドアニメーションで入ってきます。そして表示されなくなるコンテンツの場合は

3:表示されなくなる元のコンテンツのアニメーションの始点 (最初の状態) として



	.view-demo.ng-leave { 
		opacity: 1;
  		left:0%;
	}


が適用されます。

4:そして次に、表示されなくなる元のコンテンツに対して下記が適用されます。



	.view-demo.ng-leave-active {;
  		opacity: 0;
  		left:100%;
	}



表示されなくなるコンテンツに対しては最初はleft:0%;から始まり、そしてleft:100%およびopacity:0;が指定されます。すなわち、表示されなくなるコンテンツは透明になりながら、左から右側にスライドしつつ消えていきます。

下記でアニメーションの時間は1秒に設定して、position:absolute;にしてあります。どうやらngAnimateで移動させるコンテンツはposition:absolute;をかける必要があるようです。



	.view-demo.ng-enter, .view-demo.ng-leave {
  		-webkit-transition-duration: 1s;
  		-moz-transition: 1s;
  		-o-transition: 1s;
  		transition:1s
     display:block;
  		position:absolute;
	}

※ 一番上のスマホのデモはデスクトップのデモとは異なりleave-activeをleft:100%;ではなく、left:-100%;にして右ではなく左にスライドしていくようにCSSを下記のように切り替えています。


/*アニメーション*/ 
.view-demo.ng-leave { 
 	left:0%;
}

.view-demo.ng-leave.ng-leave-active {
  		left:-100%;
}

画像を除いたソースコードは全てこちらに添付しておきます。相変わらず自分自身勉強不足であるのと場当たり的に作ったので非効率な部分や不要な記述があると思いますがこちらに掲載しておきます。

ダウンロード




=======================================================

参考リンク


*ngViewドキュメント
*$routeProviderドキュメント
*ngAnimateドキュメント
*year of moo
*AngularJSでsetTimeout()やAjaxを併せて使う場合の注意点 ($scope.$apply)
*ngViewを使った時に画面を更新すると404になる場合の対策