先日のYEOMANハンズオンでgenerator-webappをベースにしてAngularJSのモジュールを入れていくのが学習効果としては高いというコメントを見たので実際にやってみました。目標としてはAngularJSとui-routerが使えれば良いかなということで次の通りとした。
- angularのインストール
- ui-routerのインストール
- ルーティングの実装
- ビルドの実施
前準備
前準備として generator-webapp を登録します:
$ npm install -g generator-webapp
次にプロジェクトを作成します。今回はオプションをすべて利用してみます:
$ mkdir webapp && cd $_ $ yo webapp _-----_ | | .--------------------------. |--(o)--| | Welcome to Yeoman, | `---------´ | ladies and gentlemen! | ( _´U`_ ) '--------------------------' /___A___\ | ~ | __'.___.'__ ´ ` |° ´ Y ` Out of the box I include HTML5 Boilerplate, jQuery, and a Gruntfile.js to build your app. ? What more would you like? ◉ Bootstrap ◉ Sass ❯◉ Modernizr
サーバを起動して動作確認します:
$ grunt serve
AngularJSのbowerインストール
bowerサーチのサイトに行くとangularと名の付くモジュールが多く存在しますが今回は「angular」を利用します
bowerインストールでAngularJSの登録
$ bower install angular --save bower angular#* cached git://github.com/angular/bower-angular.git#1.3.8 bower angular#* validate 1.3.8 against git://github.com/angular/bower-angular.git#* bower angular#~1.3.8 install angular#1.3.8 angular#1.3.8 bower_components/angular $
bower.jsonにも追加されているのが確認できます:
{ "name": "webapp", "private": true, "dependencies": { "bootstrap-sass-official": "~3.2.0", "modernizr": "~2.8.2", "angular": "~1.3.8" } }
angularの実行確認
HTMLのbodyタグに「ng-app」を追加、「{{1+1}}」を記載し「2」になれば問題無しです:
HTMLは次の通りです(部分的に書き出してます):
<body ng-app> {{1+1}}
画面の構成
webappには
- ヘッダー部
- コンテンツ部
- フッター部
があるのでコンテンツ部のみui-viewで切り替えるようにします。ヘッダーとフッターはng-includeを利用します:
またページは
- home
- about
- contact
がありますので3ページ作ることとします。
ui-routerの登録
まず ui-router を登録します:
$ bower install ui-router --save bower ui-router#* cached git://github.com/angular-ui/ui-router.git#0.2.13 bower ui-router#* validate 0.2.13 against git://github.com/angular-ui/ui-router.git#* bower ui-router#~0.2.13 install ui-router#0.2.13 ui-router#0.2.13 bower_components/ui-router └── angular#1.3.8 $
先ほど同様にbower.jsonは次の通りです:
{ "name": "webapp", "private": true, "dependencies": { "bootstrap-sass-official": "~3.2.0", "modernizr": "~2.8.2", "angular": "~1.3.8", "ui-router": "~0.2.13" } }
ファイル分割
ヘッダー、フッターとコンテンツにファイルを分割します。index.htmlは
<div class="container"> <div ng-include="'components/header/header.html'"></div> <div ui-view=""></div> <div ng-include="'components/footer/footer.html'"></div> </div>
header.htmlとfooter.htmlはappディレクトリの直下にcomponentsディレクトリを作成し配置していきます。header.htmlは次の通り:
<div class="header"> <ul class="nav nav-pills pull-right"> <li class="active"><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a></li> </ul> <h3 class="text-muted">webapp</h3> </div>
footer.htmlは次の通り:
<div class="footer"> <p><span class="glyphicon glyphicon-heart"></span> from the Yeoman team</p> </div>
main.jsの変更
main.js をangularモジュールの読み込み元とするため次のように変更します。ここでui-routerの宣言も行います:
'use strict'; angular.module('webapp', [ 'ui.router' ]) .config(['$locationProvider', '$urlRouterProvider', function ($locationProvider, $urlRouterProvider) { $urlRouterProvider .otherwise('/'); $locationProvider.html5Mode(true); } ]);
homeの定義
home画面(最初に表示される画面)を定義するため
- home.js
- home.controller.js
- home.html
- home.scss
を定義します。コントローラー名は「HomeCtrl」としています。index.htmlにhome.jsとhome.controller.jsを読み込ませることを忘れずに。
<!-- build:js({app,.tmp}) scripts/main.js --> <script src="scripts/main.js"></script> <script src="scripts/home/home.controller.js"></script> <script src="scripts/home/home.js"></script> <!-- endbuild -->
home.jsは:
'use strict'; angular.module('webapp') .config(['$stateProvider', function ($stateProvider) { $stateProvider .state('home', { url: '/', templateUrl: 'scripts/home/home.html', controller: 'HomeCtrl' }); }]);
home.controller.jsは:
'use strict'; angular.module('webapp') .controller('HomeCtrl', ['$scope', function ($scope) { }]);
home.htmlは:
<div class="jumbotron"> <h1>'Allo, 'Allo!</h1> <p class="lead">Always a pleasure scaffolding your apps.</p> <p><a class="btn btn-lg btn-success" href="#">Splendid! <span class="glyphicon glyphicon-ok"></span></a></p> </div> <div class="row marketing"> <div class="col-lg-6"> <h4>HTML5 Boilerplate</h4> <p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.</p> <h4>Sass</h4> <p>Sass is a mature, stable, and powerful professional grade CSS extension language.</p> <h4>Bootstrap</h4> <p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p> <h4>Modernizr</h4> <p>Modernizr is an open-source JavaScript library that helps you build the next generation of HTML5 and CSS3-powered websites.</p> </div> </div>
ここまで実装し実行してもエラーになります。内容は
Error: [$location:nobase] $location in HTML5 mode requires a
tag to be present!
ですがこれはheadタグの中に
<base href="/">
が含まれていないためで、登録します:
<!doctype html> <html class="no-js"> <head> <meta charset="utf-8"> <base href="/"> <title>webapp</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width"> <link rel="shortcut icon" href="/favicon.ico"> …以下省略 …
そうすると
jshintを修正
これだけでは実は終わって無くjshintを修正します。コンソールをみるとわかるのですが
app/scripts/home/home.js line 3 col 1 'angular' is not defined. app/scripts/main.js line 3 col 1 'angular' is not defined.
というような警告が発生しています。「angular」はグローバルですのでjshintに教える必要があります。jshintは「.jshintrc」というファイルに定義されていますので次の定義を追加します:
"globals": { "angular": true }
こうすると「.jshintrc」は
{ "node": true, "browser": true, "esnext": true, "bitwise": true, "camelcase": true, "curly": true, "eqeqeq": true, "immed": true, "indent": 2, "latedef": true, "newcap": true, "noarg": true, "quotmark": "single", "undef": true, "unused": true, "strict": true, "trailing": true, "smarttabs": true, "jquery": true, "globals": { "angular": true } }
これで実行すれば警告が消えます。
aboutの定義
about画面(最初に表示される画面)を定義するため
- about.js
- about.controller.js
- about.html
- about.scss
about.jsは
'use strict'; angular.module('webapp') .config(['$stateProvider', function ($stateProvider) { $stateProvider .state('about', { url: '/about', templateUrl: 'scripts/about/about.html', controller: 'AboutCtrl' }); }]);
about.controller.jsは
'use strict'; angular.module('webapp') .controller('AboutCtrl', ['$scope', function ($scope) { }]);
about.htmlは
about.html
contactの定義
contact画面(最初に表示される画面)を定義するため
- contact.js
- contact.controller.js
- contact.html
- contact.scss
about.jsは
'use strict'; angular.module('webapp') .config(['$stateProvider', function ($stateProvider) { $stateProvider .state('contact', { url: '/contact', templateUrl: 'scripts/contact/contact.html', controller: 'ContactCtrl' }); }]);
contact.controller.jsは
'use strict'; angular.module('webapp') .controller('ContactCtrl', ['$scope', function ($scope) { }]);
contact.htmlは
contact.html
メニュー作成
ui-routerの利用
ヘッダーのメニューでページが切り替わるように変更します。いまヘッダーは
<div class="header"> <ul class="nav nav-pills pull-right"> <li class="active"><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a></li> </ul> <h3 class="text-muted">webapp</h3> </div>
となっていますのでui-routerを利用するように変更します:
<div class="header"> <ul class="nav nav-pills pull-right"> <li class="active"><a ui-sref="home">Home</a></li> <li><a ui-sref="about">About</a></li> <li><a ui-sref="contact">Contact</a></li> </ul> <h3 class="text-muted">webapp</h3> </div>
これでルーティングの処理が追加されました:
メニュー表示を変える
メニューを押されカレントページとボタンのactiveカラーを揃えたいので今回はコントローラーを追加し制御したいと思います。ヘッダー部は
<div class="header" ng-controller="HeaderCtrl"> <ul class="nav nav-pills pull-right"> <li ng-class="{active: menu['home']}"><a ui-sref="home" ng-click="onClick('home')">Home</a></li> <li ng-class="{active: menu['about']}"><a ui-sref="about" ng-click="onClick('about')">About</a></li> <li ng-class="{active: menu['contact']}"><a ui-sref="contact" ng-click="onClick('contact')">Contact</a></li> </ul> <h3 class="text-muted">webapp</h3> </div>
このコントローラーをheader.htmlと同じところに定義(header.controller.jsとして)します:
'use strict'; angular.module('webapp') .controller('HeaderCtrl', ['$scope', function ($scope) { var MenuProperties = {'home':false, 'about':false, 'contact':false}; $scope.menu = []; for (var prop in MenuProperties) { $scope.menu[prop] = MenuProperties[prop]; } $scope.menu['home'] = true; $scope.onClick = function (pages) { for (var prop in MenuProperties) { $scope.menu[prop] = MenuProperties[prop]; } $scope.menu[pages] = true; }; }]);
ここまで完成
これでひと通り機能の追加は終わりです。結果としては次のようになります:
ビルド
ビルドを実行しビルド後の結果をブラウザで確認するためには次のコマンドを実行します
$ grunt serve:dist
現状だとエラーが発生するはずです:
HTMLがdistに含まれていないためです。修正するためにはGruntfile.jsを修正します。315行目あたりにcopy句がありますので次のように修正します:
'components/{,*/}*.html', 'scripts/{,*/}*.html',
を追加しています:
// Copies remaining files to places other tasks can use copy: { dist: { files: [{ expand: true, dot: true, cwd: '<%= config.app %>', dest: '<%= config.dist %>', src: [ '*.{ico,png,txt}', 'images/{,*/}*.webp', '{,*/}*.html', 'components/{,*/}*.html', 'scripts/{,*/}*.html', 'styles/fonts/{,*/}*.*' ] }, { src: 'node_modules/apache-server-configs/dist/.htaccess', dest: '<%= config.dist %>/.htaccess' }, { expand: true, dot: true, cwd: '.', src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*', dest: '<%= config.dist %>' }] }, styles: { expand: true, dot: true, cwd: '<%= config.app %>/styles', dest: '.tmp/styles/', src: '{,*/}*.css' } },
これで無事ビルド後のモジュールでも表示されたと思います。
最後に
webappから必要なモジュールを追加することで、どのモジュールがどのような役割をしているのか理解することができました。その他細かい理解をするには良い方法だと思います。今回の作業ではkarmaでのテストを行ったませんのでまだまだ作業としてはやることが多いです。
サブジェネレーターを利用できないためそういったカスタマイズをジェネレータにする必要もあります。学習という意味合いにおいては非常に有効ではないかと実感しました。
参考までに完成したモジュールは下記にあります:
albatrosary/webapp-add-angular · GitHub
ここまでするならgenerator-generatorでいいのではという意見もありますが、今回は学習目的でしたので良しとします。