Sugawara Ryousuke
のびすけです。 @n0bisuke 最近はIT系スポーツマンを目指してます。
自己紹介
出身: 東北(宮城生まれの岩手育ち)
twitter: @n0bisuke
facebook: sugawara.ryousuke
LIGの新入社員です。4月に上京。
お手柔らかに
フットサルやってます。
誘ってください。
よろしくお願いします!
家: ギークハウス -> 0円シェアハウス
趣味: 雪合戦 特技:わんこそば
"RomoとChatworkを使った受付システム"
自己紹介
〜最近作ってるもの〜
相棒:Romo
お客さんがきたら...!
お客さん
きたよ!
angular.module('App', [])
.service('todos', ['$rootScope', '$filter', function ($scope, $filter) {
var list = []; // ToDo リスト
// ToDo リストの変更を監視し 全 $scope に対して change:list イベントを発行する
$scope.$watch(function () {
return list;
}, function (value) {
$scope.$broadcast('change:list', value);
}, true);
var where = $filter('filter');
var done = { done: true };
var remaining = { done: false };
// リストが扱えるフィルタリング条件
this.filter = {
done: done,
remaining: remaining
};
// 完了状態の ToDo のみを抽出して返す
this.getDone = function () {
return where(list, done);
};
// 要件を受け取り新しい ToDo をリストに加える
this.add = function (title) {
list.push({
title: title,
done: false
});
};
// 引数の ToDo をリストから取り除く
this.remove = function (currentTodo) {
list = where(list, function (todo) {
return currentTodo !== todo;
});
};
// 完了状態の ToDo をリストから取り除く
this.removeDone = function () {
list = where(list, remaining);
};
// リスト内の ToDo すべての状態を引数に合わせる
this.changeState = function (state) {
angular.forEach(list, function (todo) {
todo.done = state;
});
};
}])
.controller('RegisterController', ['$scope', 'todos', function ($scope, todos) {
$scope.newTitle = '';
$scope.addTodo = function () {
todos.add($scope.newTitle);
$scope.newTitle = '';
};
}])
.controller('ToolbarController', ['$scope', 'todos', function ($scope, todos) {
$scope.filter = todos.filter;
$scope.$on('change:list', function (evt, list) {
var length = list.length;
var doneCount = todos.getDone().length;
$scope.allCount = length;
$scope.doneCount = doneCount;
$scope.remainingCount = length - doneCount;
});
$scope.checkAll = function () {
todos.changeState(!!$scope.remainingCount);
};
$scope.changeFilter = function (filter) {
$scope.$emit('change:filter', filter);
};
$scope.removeDoneTodo = function () {
todos.removeDone();
};
}])
.controller('TodoListController', ['$scope', 'todos', function ($scope, todos) {
$scope.$on('change:list', function (evt, list) {
$scope.todoList = list;
});
var originalTitle;
$scope.editing = null;
$scope.editTodo = function (todo) {
originalTitle = todo.title;
$scope.editing = todo;
};
$scope.doneEdit = function (todoForm) {
if (todoForm.$invalid) {
$scope.editing.title = originalTitle;
}
$scope.editing = originalTitle = null;
};
$scope.removeTodo = function (todo) {
todos.remove(todo);
};
}])
.controller('MainController', ['$scope', function ($scope) {
$scope.currentFilter = null;
$scope.$on('change:filter', function (evt, filter) {
$scope.currentFilter = filter;
});
}])
/*
.controller('MainController', ['$scope','$filter', function ($scope, $filter) {
$scope.todos = [];
$scope.newTitle = ''; //入力された文字
$scope.addTodo = function () {
$scope.todos.push({
title: $scope.newTitle,
done: false
});
$scope.newTitle = ''; //init
};
// フィルタリング条件モデル
$scope.filter = {
done: { done: true }, // 完了のみ
remaining: { done: false } // 未了のみ
};
// 現在のフィルタの状態モデル
$scope.currentFilter = null;
// フィルタリング条件を変更するメソッド
$scope.changeFilter = function (filter) {
$scope.currentFilter = filter;
};
var where = $filter('filter'); // filter フィルタ関数の取得
$scope.$watch('todos', function (todos) {
var length = todos.length;
$scope.allCount = length; // 総件数モデル
$scope.doneCount = where(todos, $scope.filter.done).length; // 完了件数モデル
$scope.remainingCount = length - $scope.doneCount; // 未了件数モデル
}, true);
var originalTitle; // 編集前の要件
$scope.editing = null; // 編集モードの ToDo モデルを表すモデル
$scope.editTodo = function (todo) {
originalTitle = todo.title;
$scope.editing = todo;
};
$scope.doneEdit = function (todoForm) {
if (todoForm.$invalid) {
$scope.editing.title = originalTitle;
}
$scope.editing = originalTitle = null;
};
// 全て完了/未了
$scope.checkAll = function () {
var state = !!$scope.remainingCount; // 未了にするのか完了にするのかの判定
angular.forEach($scope.todos, function (todo) {
todo.done = state;
});
};
// 完了した ToDo を全て削除
$scope.removeDoneTodo = function () {
$scope.todos = where($scope.todos, $scope.filter.remaining);
};
// 任意の ToDo を削除
$scope.removeTodo = function (currentTodo) {
$scope.todos = where($scope.todos, function (todo) {
return currentTodo !== todo;
});
};
}])
*/
.directive('mySelect', [function () {
return function (scope, $el, attrs) {
// scope - 現在の $scope オブジェクト
// $el - jqLite オブジェクト(jQuery ライクオブジェクト)
// jQuery 使用時なら jQuery オブジェクト
// attrs - DOM 属性のハッシュ(属性名は正規化されている)
scope.$watch(attrs.mySelect, function (val) {
if (val) {
$el[0].select();
}
});
};
}]);
何かのチュートリアルを真似して、
とりあえず動くものを作る。
動いて感動するけどapp.jsが悲惨なことに...
Angular.jsの勉強をしたときの思い出
Angular.jsの勉強をしたときの思い出
信じられないくらい日本語の情報が少ない!!
Angular流行ってるって言ってる割に...
日本語だと3つくらいしか
有用な記事がない
(function(angular) {
var MODULES = [];
var MODULE_GROUP = [
'controllers',
'filters',
'services',
'directives'
];
for (var i = 0, len = MODULE_GROUP.length; i < len; i++) {
angular.module(MODULE_GROUP[i],[]);
}
angular.module('App',MODULES.concat(MODULE_GROUP));
})(angular);
angular.module('App', [])
.service('todos', ['$rootScope', '$filter', function ($scope, $filter) {
var list = []; // ToDo リスト
// ToDo リストの変更を監視し 全 $scope に対して change:list イベントを発行する
$scope.$watch(function () {
return list;
}, function (value) {
$scope.$broadcast('change:list', value);
}, true);
var where = $filter('filter');
var done = { done: true };
var remaining = { done: false };
// リストが扱えるフィルタリング条件
this.filter = {
done: done,
remaining: remaining
};
// 完了状態の ToDo のみを抽出して返す
this.getDone = function () {
return where(list, done);
};
// 要件を受け取り新しい ToDo をリストに加える
this.add = function (title) {
list.push({
title: title,
done: false
});
};
// 引数の ToDo をリストから取り除く
this.remove = function (currentTodo) {
list = where(list, function (todo) {
return currentTodo !== todo;
});
};
// 完了状態の ToDo をリストから取り除く
this.removeDone = function () {
list = where(list, remaining);
};
// リスト内の ToDo すべての状態を引数に合わせる
this.changeState = function (state) {
angular.forEach(list, function (todo) {
todo.done = state;
});
};
}])
.controller('RegisterController', ['$scope', 'todos', function ($scope, todos) {
$scope.newTitle = '';
$scope.addTodo = function () {
todos.add($scope.newTitle);
$scope.newTitle = '';
};
}])
.controller('ToolbarController', ['$scope', 'todos', function ($scope, todos) {
$scope.filter = todos.filter;
$scope.$on('change:list', function (evt, list) {
var length = list.length;
var doneCount = todos.getDone().length;
$scope.allCount = length;
$scope.doneCount = doneCount;
$scope.remainingCount = length - doneCount;
});
$scope.checkAll = function () {
todos.changeState(!!$scope.remainingCount);
};
$scope.changeFilter = function (filter) {
$scope.$emit('change:filter', filter);
};
$scope.removeDoneTodo = function () {
todos.removeDone();
};
}])
.controller('TodoListController', ['$scope', 'todos', function ($scope, todos) {
$scope.$on('change:list', function (evt, list) {
$scope.todoList = list;
});
var originalTitle;
$scope.editing = null;
$scope.editTodo = function (todo) {
originalTitle = todo.title;
$scope.editing = todo;
};
$scope.doneEdit = function (todoForm) {
if (todoForm.$invalid) {
$scope.editing.title = originalTitle;
}
$scope.editing = originalTitle = null;
};
$scope.removeTodo = function (todo) {
todos.remove(todo);
};
}])
.controller('MainController', ['$scope', function ($scope) {
$scope.currentFilter = null;
$scope.$on('change:filter', function (evt, filter) {
$scope.currentFilter = filter;
});
}])
/*
.controller('MainController', ['$scope','$filter', function ($scope, $filter) {
$scope.todos = [];
$scope.newTitle = ''; //入力された文字
$scope.addTodo = function () {
$scope.todos.push({
title: $scope.newTitle,
done: false
});
$scope.newTitle = ''; //init
};
// フィルタリング条件モデル
$scope.filter = {
done: { done: true }, // 完了のみ
remaining: { done: false } // 未了のみ
};
// 現在のフィルタの状態モデル
$scope.currentFilter = null;
// フィルタリング条件を変更するメソッド
$scope.changeFilter = function (filter) {
$scope.currentFilter = filter;
};
var where = $filter('filter'); // filter フィルタ関数の取得
$scope.$watch('todos', function (todos) {
var length = todos.length;
$scope.allCount = length; // 総件数モデル
$scope.doneCount = where(todos, $scope.filter.done).length; // 完了件数モデル
$scope.remainingCount = length - $scope.doneCount; // 未了件数モデル
}, true);
var originalTitle; // 編集前の要件
$scope.editing = null; // 編集モードの ToDo モデルを表すモデル
$scope.editTodo = function (todo) {
originalTitle = todo.title;
$scope.editing = todo;
};
$scope.doneEdit = function (todoForm) {
if (todoForm.$invalid) {
$scope.editing.title = originalTitle;
}
$scope.editing = originalTitle = null;
};
// 全て完了/未了
$scope.checkAll = function () {
var state = !!$scope.remainingCount; // 未了にするのか完了にするのかの判定
angular.forEach($scope.todos, function (todo) {
todo.done = state;
});
};
// 完了した ToDo を全て削除
$scope.removeDoneTodo = function () {
$scope.todos = where($scope.todos, $scope.filter.remaining);
};
// 任意の ToDo を削除
$scope.removeTodo = function (currentTodo) {
$scope.todos = where($scope.todos, function (todo) {
return currentTodo !== todo;
});
};
}])
*/
.directive('mySelect', [function () {
return function (scope, $el, attrs) {
// scope - 現在の $scope オブジェクト
// $el - jqLite オブジェクト(jQuery ライクオブジェクト)
// jQuery 使用時なら jQuery オブジェクト
// attrs - DOM 属性のハッシュ(属性名は正規化されている)
scope.$watch(attrs.mySelect, function (val) {
if (val) {
$el[0].select();
}
});
};
}]);
ファイルやディレクトリを分割することで
app.jsがかなりスッキリした
ただ、
このやり方がベストかは分からない...
Angular.jsの勉強をしたときの思い出
皆さん、
Angular.jsで開発するときの
ディレクトリ構成って
どうしてますか?
コンポーネント毎で分けるよ
ページ毎で分けるよ
あえて
ディレクトリに
分けないよ!
色々なケースがあると思います。
Angular.jsの
ディレクトリ構成パターン紹介と、
利用して感じた考察などを話します。
今日の内容
初心者でディレクトリ構成に悩んでる人や
ベストプラクティスを求める人に響いて欲しい
1.ディレクトリ構成のパターン
2.組み込みとディレクトリ構成
1.1 Angular-Seed
app
├─ css //stylesheet
│ └─ app.css
├─ img //image
├─ js //angular.js files
│ ├─ app.js
│ ├─ controllers.js
│ ├─ directives.js
│ ├─ filters.js
│ └─ services.js
├─ lib //3rd party library
│ └─ angular
│ └─ angular.js
├─ partials // parts
│ ├─ partial1.html
│ └─ partial2.html
├─ index-async.html
└─ index.html //main html
1.1 Angular-Seed
・基本的な分け方
・シンプルで分かりやすい
・小さい規模向け
1.2 Angularjs Style Guide
・検索でヒットした4つのStyle Guide
- mgechev/angularjs-style-guide
- johnpapa/angularjs-styleguide
- toddmotto/angularjs-styleguide
- gocardless/angularjs-style-guide
・Angular.js公式のものではない
・常に議論があって流動的
・雰囲気で4種類(A〜D)に分類したので聞いて下さい笑
1.2.1 mgechev / Angularjs Style Guide
・AngularJSアプリケーションのベストプラクティスとガイドラインを提供が目的
・日本語訳あり
https://github.com/mgechev/angularjs-style-guide/blob/master/README-ja-jp.md#%E5%85%A8%E8%88%AC
・ディレクトリ構成について最も言及している
Minko Gechev (mgechev)氏
1.2.1 mgechev / Angularjs Style Guide
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── page1
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── page2
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── page1
│ │ │ └── directive1.js
│ │ └── page2
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── page1
│ │ └── page2
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── lib
└── test
A:コンポーネントタイプ分類
・app : メインのプログラムファイル
・lib: サードパーティ性のライブラリなど
・test: テスト用テストコード
appの大枠はAngular-Seedと基本同じ
上の階層をコンポーネントタイプで分けて、下の階層は機能性(ページ)で分けるアプローチ
Seedの構成を拡張した形なので構成把握はしやすい
ページが多くなると細かい編集が厳しいかも。ディレクティブの使い回しとか...
1.2.1 mgechev / Angularjs Style Guide
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── page1
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ └── filter2.js
│ │ └── services
│ │ ├── service1.js
│ │ └── service2.js
│ └── page2
│ ├── controllers
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ └── filter3.js
│ └── services
│ └── service3.js
├── lib
└── test
B:機能性分類
・app/common: 共通処理用ファイル
・app/page1: page1用ファイル
・app/page2: page2用ファイル
上の階層を機能性(ページ)で分けて、下の階層はコンポーネントタイプで分けるアプローチ
ページが多くなる場合に有効。
Seedからの拡張よりも、プロジェクト開始から設計しておきたい。
1.2.1 mgechev / Angularjs Style Guide
app
└── directives
├── directive1
│ ├── directive1.html
│ ├── directive1.js
│ └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
└── directive2.sass
ディレクティブを作成するとき、全てひとつのフォルダ内に入れてディレクティブファイル(テンプレート、CSS/SASS, JavaScript)として関連付けてしまうと便利
コンポーネントタイプ分類/機能性分類のどちらでも使える
services
├── cache
│ ├── cache1.js
│ └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js
1.2.1 mgechev / Angularjs Style Guide
特定のコンポーネントに変更を加えた時にそのテストを見つけるのが簡単になり、テスト自体がマニュアルやショーケースのように。
テストコードの場所
1.2.1 mgechev / Angularjs Style Guide
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── page1
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── page2
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── page1
│ │ │ └── directive1.js
│ │ └── page2
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── page1
│ │ └── page2
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── lib
└── test
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── page1
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── page2
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── page1
│ │ │ └── directive1.js
│ │ └── page2
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── page1
│ │ └── page2
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ ├── cache1.spec.js
│ │ ├── cache2.js
│ │ └── Cache2.spec.js
│ └── models
│ ├── Model1.js
│ ├── Model1.spec.js
│ ├── Model2.js
│ └── Model2.spec.js
└── lib
テストを内包
関連するコードを近くに置いた方が管理はしやすい。
ファイル数が量が増えてくると汚くなってしまう。
testにまとまっていた方がgulpタスクなどは書きやすい
1.2.2 johnpapa / angularjs-styleguide
・チーム開発のための AngularJS スタイルガイド
・toddomoddo氏とディスカッションして策定
・更新頻度が高い (最新はコミットは一昨日 数時間前)
・ディレクトリ構成についての記載あり
jonpapa氏
1.2.2 johnpapa / Angularjs Style Guide
app/
app.module.js
app.config.js
app.routes.js
components/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
layout/
shell.html
shell.controller.js
topnav.html
topnav.controller.js
people/
attendees.html
attendees.controller.js
speakers.html
speakers.controller.js
speaker-detail.html
speaker-detail.controller.js
services/
data.service.js
localstorage.service.js
logger.service.js
spinner.service.js
sessions/
sessions.html
sessions.controller.js
session-detail.html
session-detail.controller.js
・Componnts: ディレクティブ群
・layout: 共通部品
・people: ページ毎のファイル
・services: サービス群
・sessions: セッション機能
(機能毎にフォルダが増える)
C: コンポーネントタイプ分類 + 機能性分類の並列
ディレクトリがかなり多くなってしまうかも
階層が深くならないのでファイルは見つかりやすい
1.2.2 johnpapa / Angularjs Style Guide
app/
app.module.js
app.config.js
app.routes.js
controllers/
attendees.js
session-detail.js
sessions.js
shell.js
speakers.js
speaker-detail.js
topnav.js
directives/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
services/
dataservice.js
localstorage.js
logger.js
spinner.js
views/
attendees.html
session-detail.html
sessions.html
shell.html
speakers.html
speaker-detail.html
topnav.html
D: コンポーネントタイプ分類からViewsを分離
"CよりもA寄り"
ファイルが多くなってくると煩雑になるので、各ディレクトリで更にディレクトリを切ると良いかも
・controllers: コントローラー群
・directives: ディレクティブ群
・services: サービス群
・views: ページ毎のファイル
Cからviewsを分離したイメージ
このタイプが割と多い
1.2.3 toddmotto / angularjs-styleguide
・チーム開発のための AngularJS スタイルガイド
・johnpapa氏とディスカッションして策定
・氏曰く、johnpapa氏のスタイルガイドより良い(?)
・日本語訳の"tama3bb/angularjs-styleguide"もある
https://github.com/tama3bb/angularjs-styleguide
・ディレクトリ構成についての記載は無かったので紹介のみ
toddmotto氏
1.2.4 gocardless / angularjs-style-guide
・前述したTodd Motto’s styleguide , John Papa’s styleguide, Minko Gechev’s styleguideの3つを参考にしている。
・後述しますが、An AngularJS Style Guide for Closure Users at Googleも参照しているらしい
・ディレクトリ構成についての記載は少し
http://gocardless.com
/app
/components
/alert
alert.directive.js
alert.directive.spec.js
alert.template.html
/config
main.config.js
/constants
api-url.constant.js
/routes
/customers
/index
customers-index.template.html
customers-index.route.js
customers-index.controller.js
customers-index.e2e.js
/helpers
/currency
currency-filter.js
currency-filter.spec.js
/unit
/e2e
/services
/creditors
creditors.js
creditors.spec.js
bootstrap.js
main.js
/assets
/fonts
/images
/stylesheets
404.html
index.html
1.2.4 gocardless / angularjs-style-guide
今までの例と比べ、詳細な設計。
初めに設計をしておけば死角は少ないかも。
・routes: ページ毎のファイル
・assets: フォントや画像,CSSなどの静的ファイル
サーバーサイドのフレームワークの構成に類似しているので、サーバーサイドエンジニアが理解しやすい。
C: コンポーネントタイプ分類 + 機能性分類の並列
1.3 AngularJS Best Practices: Directory Structure
・kukicadnan氏によるコラム
・ John Papa氏とTodd Motto氏のスタイルガイドを参考にしているらしい
・http://scotch.io/tutorials/javascript/angularjs-best-practices-directory-structure
1.3.1 Standard Structure
.
├──app
│ ├── controllers
│ │ ├── mainController.js
│ │ └── otherController.js
│ │
│ ├── directives
│ │ ├── mainDirective.js
│ │ └── otherDirective.js
│ │
│ ├── services
│ │ ├── userServices.js
│ │ └── itemServices.js
│ │
│ ├── js
│ │ ├── bootstrap.js
│ │ └── jquery.js
│ │
│ └── app.js
│
└--views
├── mainView.html
├── otherView.html
└── index.html
・app : メインのプログラムファイル
・view: ページ毎のファイル
appフォルダ内はAngular-Seedと基本同じ
D: コンポーネントタイプよりの機能性分類
johnpapaの構成に似てるけど、あちらはapp/viewsという構成
htmlを独立させるので、htmlやCSSだけを触るコーダーやデザイナーがいるときは作業分担しやすい。
1.3.2 A Better Structure and Foundation
.
├──app
│ ├── shared
│ │ ├── sidebar
│ │ │ ├── sidebarDirective.js
│ │ │ └── sidebarView.html
│ │ │
│ │ └── article
│ │ ├── articleDirective.js
│ │ └── articleView.html
│ │
│ ├── components
│ │ ├── home
│ │ │ ├── homeController.js
│ │ │ ├── homeService.js
│ │ │ └── homeView.html
│ │ │
│ │ └── blog
│ │ ├── blogController.js
│ │ ├── blogService.js
│ │ └── blogView.html
│ │
│ ├── app.module.js
│ └── app.routes.js
│
├--assets
│ ├── img
│ ├── css
│ ├── js
│ └── libs
│
└── index.html
B: 機能性分類
gocardlessやmgechev(minko)の構成に似てるけど、
sharedというフォルダに部品を格納
基本的にページ分類になるけど、共通部品ディレクトリを外部に出して管理しやすくしている。
1.4 Building Huuuuuge Apps
with AngularJS
.
├─ index.html
├─ scripts
│ ├─ controllers
│ │ └─ main.js
│ │ └─ ...
│ ├─ directives
│ │ └─ myDirective.js
│ │ └─ ...
│ ├─ filters
│ │ └─ myFilter.js
│ │ └─ ...
│ ├─ services
│ │ └─ myService.js
│ │ └─ ...
│ ├─ vendor
│ │ ├─ angular.js
│ │ ├─ angular.min.js
│ │ ├─ es5-shim.min.js
│ │ └─ json3.min.js
│ └─ app.js
├─ styles
│ └─ ...
└─ views
├─ main.html
└─ ...
johnpapaの構成に似ている。
個人的にはvendor(他だとlib)の
位置が気になる。
D: コンポーネントタイプ分類からViewsを分離
1.5 AngularJS Folder Structure |
Stackoverflow
How do you layout a folder structure for a large and scaleable AngularJS application?
1.5.1 answer ①
johnpapa構成 D
johnpapa構成 C
/app
/scripts
/controllers
/directives
/services
/filters
app.js
/views
/styles
/img
/bower_components
index.html
bower.json
/scripts
scripts.min.js (all JS concatenated, minified and grunt-rev)
vendor.min.js (all bower components concatenated, minified and grunt-rev)
/views
/styles
mergedAndMinified.css (grunt-cssmin)
/images
index.html (grunt-htmlmin)
After build...
Before build...
1.5.2 answer ②
Seed拡張
1.6 An AngularJS Style Guide for Closure Users at Google
sampleapp/ the client app and its unit tests live under this directory
app.css
app.js
app-controller.js
app-controller_test.js
components/ module def'n, services, directives, filters, and related
foo/ files live here. "foo" describes what the module does.
foo.js The 'foo' module is defined here.
foo-directive.js
foo-directive_test.js
foo-service.js
foo-service_test.js
index.html
e2e-tests/ e2etests are outside the scope of this
sampleapp-backend/ proposal, but could be at the same level as
backend/ and sampleapp/... we leave this
up to the developer.
Consider a very simple app with one directive and one service:
sampleapp/
app.css
app.js
app-controller.js
app-controller_test.js
components/
bar/ "bar" describes what the service does
bar.js
bar-service.js
bar-service_test.js
foo/ "foo" describes what the directive does
foo.js
foo-directive.js
foo-directive_test.js
index.html
Or, in the case where the directive and the service are unrelated, we'd have:
sampleapp/
app.css
app.js top-level configuration, route def’ns for the app
app-controller.js
app-controller_test.js
components/
adminlogin/
adminlogin.css styles only used by this component
adminlogin.js optional file for module definition
adminlogin-directive.js
adminlogin-directive_test.js
private-export-filter/
private-export-filter.js
private-export-filter_test.js
userlogin/
somefilter.js
somefilter_test.js
userlogin.js
userlogin.css
userlogin.html
userlogin-directive.js
userlogin-directive_test.js
userlogin-service.js
userlogin-service_test.js
index.html
subsection1/
subsection1.js
subsection1-controller.js
subsection1-controller_test.js
subsection1_test.js
subsection1-1/
subsection1-1.css
subsection1-1.html
subsection1-1.js
subsection1-1-controller.js
subsection1-1-controller_test.js
subsection1-2/
subsection2/
subsection2.css
subsection2.html
subsection2.js
subsection2-controller.js
subsection2-controller_test.js
subsection3/
subsection3-1/
etc...
For a more complex app -- for example, a ticketing application that has a user and an admin login, where some sections of the UI and routing are common to both users while other subsections are only available to one or the other, we want to isolate files belonging to each component, thus:
1.7 Yeoman
・フロントエンド開発を便利にしてくれるジェネレータ
・Angular.jsのジェネレータも
O'Reilly本だとプロジェクト作成時にYeomanでひな形を作るのを推奨している
$ npm install -g yo
$ npm install -g generator-angular
$ yo angular [app-name]
$ yo angular:route myroute
$ yo angular:controller user
$ yo angular:directive myDirective
$ yo angular:filter myFilter
$ yo angular:view user
$ yo angular:service myService
$ yo angular:decorator serviceName
1.7 Yeoman/generator-angular
.
├── test
│ ├── spec
│ │ └── ...(app/scriptsと同じ構成)
│ ├── .jshintrc
│ └── karma.conf.js
├── bower_components
├── node_modules
├── app
│ ├── images
│ ├── scripts
│ │ ├── controllers
│ │ │ ├── main.js
│ │ │ ├── myroute.js
│ │ │ └── user.js
│ │ ├── decorators
│ │ │ └── servicenamedecorator.js
│ │ ├── directives
│ │ │ └── mydirectives.js
│ │ ├── filters
│ │ │ └── myfilters.js
│ │ ├── services
│ │ │ └── myservice.js
│ │ └── app.js
│ ├── styles
│ └── views
│ ├── main.html
│ ├── myroute.html
│ └── user.html
├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── bower.json
├── Gruntfile.js
└── package.json
1.7 Yeoman/generator-angular
appの中に開発ファイルが全て入るので
最終的なbuild時のターゲットが分かりやすい。管理しやすい。
testが離れているとファイル追加時に手間がありそう。Yeomanで解決?
O'Reillyが推してるので、
割と公式認定?
D: コンポーネントタイプ分類からViewsを分離
1.ディレクトリ構成のパターン
2.組み込みとディレクトリ構成
ここまでの話はAngular.js単体で利用する場合の話でした。
ここからはサーバーサイドに
組み込むときに生じる問題や
ジレンマだったりを紹介します。
2.1 組み込み時の構成の実例
〜Expressへの組み込み〜
・Node.js向けのMVCフレームワーク
・RubyのSinatraライク
・今回の事例はv3系
.
├── node_modules
├── modules
├── public
├── routes
├── views
├── app.js
└── package.json
2.1 Express開発時の実際の構成
.
├── node_modules
├── modules
├── public
│ ├── stylesheets
│ ├── images
│ └── javascripts
│ ├── controllers
│ ├── filters
│ ├── services
│ └── app.js
├── routes
├── views
│ ├── view1.ejs
│ ├── view2.ejs
│ └── common
│ └── common.ejs
├── app.js
└── package.json
・組み込み時のディレクトリで開発
・API設計では無い
・タスクランナーは利用していない
こうした方が良かったかも(?)
root
├── node_modules
├── modules
├── public
│ ├── stylesheets
│ ├── images
│ └── javascripts
│ ├── controllers
│ ├── filters
│ ├── services
│ └── app.js
├── routes
├── views
│ ├── view1.ejs
│ ├── view2.ejs
│ └── common
│ └── common.ejs
├── app.js
└── package.json
フロント開発時のコードとビルド後の分離
root
├── app
│ ├── controllers
│ ├── filters
│ ├── services
│ ├── app.js
│ └── package.json
├── node_modules
├── modules
├── public
│ ├── stylesheets
│ ├── images
│ └── javascripts
│ └── app.min.js
├── routes
├── views
│ ├── view1.ejs
│ ├── view2.ejs
│ └── common
│ └── common.ejs
├── app.js
└── package.json
package.jsonがカオスになる問題
2.1 Express開発時の実際の構成
.
├── node_modules
├── modules
├── public
│ ├── stylesheets
│ ├── images
│ └── javascripts
│ ├── controllers
│ ├── filters
│ ├── services
│ └── app.js
├── routes
├── views
│ ├── view1.ejs
│ ├── view2.ejs
│ └── common
│ └── common.ejs
├── app.js
└── package.json
・組み込み時のディレクトリで開発
・API設計にはなっていない
・タスクランナーは利用していない
納品後はクライアントが保守
Socket.ioな開発はサーバーとフロントの境目が少ない
プロジェクトの途中からAngular.jsを導入
このケースから学んだ
ディレクトリ構成を考えるポイント
・サーバーエンジニアがフロントのコードを触る可能性があるか
- サーバー側の言語はJavascriptか
・途中からAngular.jsを導入した場合
・タスクランナーを利用するか
〜CodeIgniter + Smartyへの組み込み〜
2.2 組み込み時の構成の実例
CodeIgniterの基本ディレクトリ構成
・今回のCodeIgniterバージョンは2系
・SmartyなどのAngular.jsと競合するエンジンが使われていないか
・PC版とSP版が分かれているか
〜WordPressへの組み込み〜
2.3 組み込み時の構成の実例
反省を踏まえた最近の弊社の構成
.
┣ bower_components/
┣ common.js
┣ app.js
┣ controllers/
┃┗ ○○/
┃ ┣ ○○Ctrl.js
┃ ┗ □□Ctrl.js
┣ services/
┃┣ ○○Model.js
┃┗ ○○.js
┣ directives/
┃┗○○/
┃ ┣ ○○.js
┃ ┗ ○○.html
┣ filters/
┃┗ ○○.js
┣ views/
┃ ┗ ○○/
┃ ┗ ○○.html
┗ test/
┣ controllers/
┃ ┗ ○○
┃ ┗ ○○CtrlSpec.js
┣ services
┃┗ □□Spec.js
┣ directives
┃┗ ○○
┃ ┗ ○○Spec.js
┗ filters
┗ ○○Spec.js
スッキリした構成で今のところ最新
改善途中です。
servicesの中でmodelの分離はしていないけど今後どうしようか。など
common.js:
bowerの外部ライブラリをまとめる
app.js:
angular.jsのファイルをまとめる
・Angular.jsのディレクトリ構成パターンを調べてまとめた
・style guide 4つは是非見て欲しい
・実例であった話も紹介
今回紹介した事例が今後の開発の参考になればと思います。
ケースによって内容は変わると思うので
ウチではこうしてるよ!
みたいな事例などを懇親会でお聞きしたいです。
まとめ
おわり。
By Sugawara Ryousuke