このスタイルガイドの目的はAngularJSアプリケーションのベストプラクティスとスタイルガイドラインを提供することです。 これらのベストプラクティスは以下から集めたものです:
- AngularJSソースコード
- 私が読んだコードや文章
- 私の経験
注意1: このスタイルガイドは草稿であり、その主な目的はコミュニティ駆動にすることです。足りない部分を補うことはコミュニティ全体から大きな賞賛を受けることになります。
注意2: 翻訳版のガイドラインを読み始める前に、それが最新の状態であるか確認しましょう。英語版のAngularJSスタイルガイドが最新版となります。
当ガイドラインは、JavaScript開発のガイドラインではありません。JavaScript開発のガイドラインはこちらで見つけることができます:
- Google JavaScript スタイルガイド
- Mozilla JavaScript スタイルガイド
- Douglas Crockford JavaScript スタイルガイド
- Airbnb JavaScript スタイルガイド
AngularJSの開発をする上でのおすすめはGoogle JavaScript スタイルガイドです。
AngularJSのGitHub WikiにProLoserの書いた類似のセクションがあります。こちらで確認することができます。
- German
- Spanish
- French
- Indonesian
- Italian
- Japanese
- Korean
- Polish
- Portuguese
- Russian
- Serbian
- Serbian lat
- Chinese
- Turkish
規模の大きなAngularJSのアプリケーションは複数のコンポーネントを持つため、ディレクトリ階層でコンポーネントを構造化するのがよいでしょう。 主に2つのアプローチがあります:
- 上位の階層をコンポーネントの種類で分けて、下位の階層は機能性で分ける。
この場合のディレクトリ構造は以下のようになります:
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── home
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── about
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── home
│ │ │ └── directive1.js
│ │ └── about
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── home
│ │ └── about
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── partials
├── lib
└── test
- 上位の階層を機能性で分けて、下位の階層はコンポーネントの種類で分ける。
レイアウトは以下のとおりです:
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── home
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ └── filter2.js
│ │ └── services
│ │ ├── service1.js
│ │ └── service2.js
│ └── about
│ ├── controllers
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ └── filter3.js
│ └── services
│ └── service3.js
├── partials
├── lib
└── test
- ディレクトリ名に複数の単語が含まれる場合は、lisp-caseシンタックスで記述します:
app
├── app.js
└── my-complex-module
├── controllers
├── directives
├── filters
└── services
- ディレクティブに関連するファイル(例:templates, CSS/SASS files, JavaScript)は全て1つのディレクトリに格納しています。このスタイルを選択する場合、プロジェクト全体に一貫してこのスタイルを適用します。
app
└── directives
├── directive1
│ ├── directive1.html
│ ├── directive1.js
│ └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
└── directive2.sass
このアプローチは、上記のそれぞれのディレクトリ構造と組み合わせることができます。
- コンポーネントに対するユニット・テストは対象のコンポーネントが位置するディレクトリ内に置いてしまうべきです。特定のコンポーネントに変更を加えた際に、容易にテストを見つけることができます。テストはドキュメントやユースケースのような存在になります。
services
├── cache
│ ├── cache1.js
│ └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js
app.js
ファイルにはルート定義、設定、(もし必要なら)手動のブートストラップも含まれるべきです。- 1つのJavaScriptファイルには、1つのコンポーネントのみがあるようにします。ファイル名にはコンポーネント名を付けます。
- Yeomanやng-boilerplateのようなAngularプロジェクト構造のテンプレートを使いましょう。
コンポーネントの命名に関する慣例は、各コンポーネントのセクションで見ることができます。
TLDR; scriptは一番下に配置します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MyApp</title>
</head>
<body>
<div ng-app="myApp">
<div ng-view></div>
</div>
<script src="angular.js"></script>
<script src="app.js"></script>
</body>
</html>
シンプルに保ちましょう。AngularJS固有のディレクティブは後ろに配置しましょう。コードが見やすくなりますし、フレームワークによって拡張されたHTMLを見つけやすくなります(保守性も高くなります)。
<form class="frm" ng-submit="login.authenticate()">
<div>
<input class="ipt" type="text" placeholder="name" require ng-model="user.name">
</div>
</form>
その他のHTML属性はCode Guideの方針に従うのがよいでしょう。
- これらを使うようにしましょう:
setTimeout
の代わりに$timeout
setInterval
の代わりに$interval
window
の代わりに$window
document
の代わりに$document
$.ajax
の代わりに$http
テストがしやすくなり、また、予期しない動作を防ぐことができます(例えば、 $scope.$apply
を setTimeout
内に書き忘れる)。
-
以下のツールを使用してワークフローを自動化しましょう:
-
コールバックの代わりにpromise(
$q
)を使います。コードはよりエレガントですっきりとしますし、コールバック地獄から解放されます。 -
できるだけ
$http
の代わりに$resource
を使います。抽象性を高めることにより冗長なコードから解放されます。 -
AngularJS pre-minifier(ng-annotate)を使うことで、minifyした後に発生する問題を回避しましょう。
-
グローバル変数を使用してはいけません。依存性の注入を使って全ての依存関係を解決することで、バグやテスト時のモンキーパッチを防ぎます。
-
GruntやGulpを使ってコードをIIFE(Immediately Invoked Function Expression)にラップすることによってglobalを使わないようにしましょう。プラグインとしてはgrunt-wrap や gulp-wrap があります。Gulpを使った場合の例です。
gulp.src("./src/*.js") .pipe(wrap('(function(){\n"use strict";\n<%= contents %>\n})();')) .pipe(gulp.dest("./dist"));
-
$scope
を汚染してはいけません。テンプレートで使用するメソッドや変数のみ追加しましょう。 -
ngInit
の代わりにcontrollerの使用を優先します。ngRepeat
のプロパティのエイリアスを作る場合にのみngInit
を利用します。このケースに加え、スコープの変数を初期化する際にもngInit
よりもcontrollerを利用するべきです。ng-init
に渡されたエクスプレッションは$parse
サービスに実装されたAngularのインタープリタによって字句解析されパースされ、評価されます。これは次のことを引き起こします。- インタープリタはJavaScriptで実装されているのでパフォーマンスに影響が出ます。
$parse
サービス内でのパース済みエクスプレッションのキャッシュはうまい具外に働からないことが多いです。ng-init
エクスプレッションが多くの場合1度しか実行されないからです。- エラーを起こしやすくなります。文字列をテンプレートに書くことになるので、エディタのシンタックスハイライトやその他のサポートが効きません。
- ランタイムエラーがでません。
-
変数名やメソッド名に
$
プレフィックスを使ってはいけません。このプレフィックスはAngularJSによって予約されています。 -
AngularJSの依存性の注入メカニズムによって依存性の解決を行う際には、AngularJSのビルトイン、カスタムという順に並べます。
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
return {
//Something
};
});
-
モジュールはlowerCamelCaseで命名します。モジュール
b
がモジュールa
のサブモジュールである場合、a.b
のようにネームスペースを利用してネストすることができます。モジュールを構造化する方法は一般的に2つあります:
- 機能性
- コンポーネントタイプ
今現在、2つに大きな違いはありませんが、1.の方法がより整って見えます。また、もし遅延ローディング・モジュールが実装されたら(AnglarJSのロードマップにはありませんが)、アプリケーションのパフォーマンスが向上するでしょう。
-
コントローラ内でDOMを操作してはいけません。テストがしづらくなりますし、関心の分離の原則を破ることになります。代わりにティレクティブを使いましょう。
-
コントローラ名は、そのコントローラの機能を表す名前(例: shopping cart, homepage, admin panel)にし、最後に
Ctrl
を付けます。 -
コントローラは素のJavascriptなので(constructors)、命名はUpperCamelCase(
HomePageCtrl
,ShoppingCartCtrl
,AdminPanelCtrl
, etc.)を使います。 -
コントローラはグローバルな名前空間に定義してはいけません。(たとえAngularJSが許可しても、グローバルな名前空間を汚染するバッドプラクティスになります)。
-
コントローラの定義には下記のシンタックスを使いましょう:
function MyCtrl(dependency1, dependency2, ..., dependencyn) { // ... } module.controller('MyCtrl', MyCtrl);
minifyの問題を回避するために、ng-annotateや(grunt task grunt-ng-annotate)などの標準的なツールを使って配列定義シンタックスを自動的に生成することができます。
-
controller as
シンタックスを使いましょう。<div ng-controller="MainCtrl as main"> {{ main.title }} </div>
app.controller('MainCtrl', MainCtrl); function MainCtrl () { this.title = 'Some title'; };
このシンタックスを使う利点は下記のとおりです:
- '分離' されたコンポーネントが作成される。バインドされたプロパティは
$scope
プロトタイプ・チェーンに含まれません。$scope
プロトタイプ継承は大きな欠点(多分この欠点のために Angular 2 では採用されていません)があるのでこれは良いやり方です。- データがどこから来たのかわからない。
- スコープの値の変更が想定していないところに影響する。
- リファクタが大変になる。
- 'ドット・ルール' 。
- 特別な事情(
$scope.$broadcast
など )がない限り、$scope
は使わないようにしましょう。これはAngularJS V2 への良い備えになります。 - シンタックスは 'vanilla' JavaScriptのコンストラクタに近いです。
controller as
について詳しくは、 digging-into-angulars-controller-as-syntax を参照してください。 - '分離' されたコンポーネントが作成される。バインドされたプロパティは
-
配列で定義する場合は、依存性の正しい名前を使いましょう。コードが読みやすくなります:
function MyCtrl(s) { // ... } module.controller('MyCtrl', ['$scope', MyCtrl]);
次に書くようにすることで読みやすくなります:
function MyCtrl($scope) { // ... } module.controller('MyCtrl', ['$scope', MyCtrl]);
特にスクロールしなければ読みきれないようなコードを含んだファイルに適用します。どの変数がどの依存性に結びついているのか忘れてしまうことを防げます。
-
なるべく無駄のないようにコントローラを作りましょう。抽象的で広く使われているロジックはサービス内に入れましょう。
-
ビジネスロジックをコントローラ内に書かないようにしましょう。ビジネスロジックは、サービスを使って
model
に委譲します。 例://これはビジネスロジックをコントローラの中に書く良く行われる例です(悪い例ですが) angular.module('Store', []) .controller('OrderCtrl', function ($scope) { $scope.items = []; $scope.addToOrder = function (item) { $scope.items.push(item);//-->Business logic inside controller }; $scope.removeFromOrder = function (item) { $scope.items.splice($scope.items.indexOf(item), 1);//-->Business logic inside controller }; $scope.totalPrice = function () { return $scope.items.reduce(function (memo, item) { return memo + (item.qty * item.price);//-->Business logic inside controller }, 0); }; });
model
にビジネスロジックを委譲すると、コントローラはこのようになります()。 When delegating business logic into a 'model' service, controller will look like this (サービスモデルの実装はサービスの項目で確認できます)://Orderは `model` として扱われています angular.module('Store', []) .controller('OrderCtrl', function (Order) { $scope.items = Order.items; $scope.addToOrder = function (item) { Order.addToOrder(item); }; $scope.removeFromOrder = function (item) { Order.removeFromOrder(item); }; $scope.totalPrice = function () { return Order.total(); }; });
どうしてビジネスロジックとステートをコントローラの中に書くのが悪いのでしょうか?
- コントローラはそれぞれのビューで生成され、ビューがアンロードされた時に消滅します。
- コントローラはビューと結びついているので再利用可能なものではありません。
- コントローラはインジェクションできません。
Example:
// app.js /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Custom events: - 'authorization-message' - description of the message - { user, role, action } - data format - user - a string, which contains the username - role - an ID of the role the user has - action - specific ation the user tries to perform * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-
データのフォーマットロジックを、filter内にカプセル化する必要がある場合、このように依存関係を宣言します:
function myFormat() { return function () { // ... }; } module.filter('myFormat', myFormat); function MyCtrl($scope, myFormatFilter) { // ... } module.controller('MyCtrl', MyCtrl);
-
ネストしたコントローラを利用する場合、ネストスコープ(
controllerAs
シンタックス)を使います。app.js
module.config(function ($routeProvider) { $routeProvider .when('/route', { templateUrl: 'partials/template.html', controller: 'HomeCtrl', controllerAs: 'home' }); });
HomeCtrl
function HomeCtrl() { this.bindingValue = 42; }
template.html
<div ng-bind="home.bindingValue"></div>
- ディレクティブ名はlowerCamelCaseで記述します。
- link関数では
$scope
の代わりにscope
を使用します。compileメソッドやpre/post link関数が呼び出されるときには、引数は定義済みです。DIを使用してそれらを変更することはできません。この方式はAngularJSのソースコードでも使用されています。 - サードパーティ製ライブラリとの名前空間の衝突を防ぐために、新たに作成するディレクティブ名にはプレフィックスを付けましょう。
ng
やui
などのプレフィックスは使わないようにしましょう。これらはAngularJSやAngularJS UIによって予約されています。- DOMの操作は全てディレクティブを介してのみ行うようにします。
- 再利用可能なコンポーネントを開発する際は、分離スコープを作りましょう。
- ディレクティブはコメントやクラスではなく、属性やエレメントとして使います。これによって可読性が上がります。
- 後片付けのために
scope.$on('$destroy', fn)
を使いましょう。特にサードパーティー製のプラグインをディレクティブとして利用する際に便利です。 - 信用出来ない内容を扱う際には
$sce
を忘ずに使いましょう。
- フィルタ名はlowerCamelCaseで記述します。
- できるだけ軽量なフィルタを作りましょう。フィルタは
$digest
ループ内で頻繁に呼ばれるため、フィルタが遅いとアプリ全体が遅くなります。 - 明瞭さを保つために1つのフィルタでは1つのことだけをやらせましょう。複雑な操作は既存のフィルタのパイプで行います。
このセクションにはAngularJSのサービスコンポーネントについての情報を含みます。特に言及されていない限り、定義方法(例: プロバイダ、 .factory
、 .service
)と関係しています。
-
サービス名はcamelCaseで記述します。
-
コンストラクタ関数として利用される場合、サービス名はUpperCamelCase(PascalCase)で記述します。例:
function MainCtrl($scope, User) { $scope.user = new User('foo', 42); } module.controller('MainCtrl', MainCtrl); function User(name, age) { this.name = name; this.age = age; } module.factory('User', function () { return User; });
-
その他のサービス名はlowerCamelCaseで記述します。
-
-
ビジネスロジックはカプセル化してサービスに入れます。
model
として利用するのがよいでしょう。例えば://Order is the 'model' angular.module('Store') .factory('Order', function () { var add = function (item) { this.items.push (item); }; var remove = function (item) { if (this.items.indexOf(item) > -1) { this.items.splice(this.items.indexOf(item), 1); } }; var total = function () { return this.items.reduce(function (memo, item) { return memo + (item.qty * item.price); }, 0); }; return { items: [], addToOrder: add, removeFromOrder: remove, totalPrice: total }; });
Controllersの項目でコントローラがこのサービスを使った例はを確認できます。
-
問題領域(ドメイン)に関わる処理を行うサービスは
factory
の代わりにservice
を利用するのがよいでしょう。"klassical"な継承を利用できるメリットがあります:function Human() { //body } Human.prototype.talk = function () { return "I'm talking"; }; function Developer() { //body } Developer.prototype = Object.create(Human.prototype); Developer.prototype.code = function () { return "I'm coding"; }; myModule.service('Human', Human); myModule.service('Developer', Developer);
-
セッションレベルでのキャッシュには
$cacheFactory
が使えます。これはリクエスト結果をキャッシュしたい時や重い処理をキャッシュしたいときに使えます。 -
設定が必要なサービスを利用する場合は、サービスをプロバイダとして利用し、
config
コールバックで設定をします。angular.module('demo', []) .config(function ($provide) { $provide.provider('sample', function () { var foo = 42; return { setFoo: function (f) { foo = f; }, $get: function () { return { foo: foo }; } }; }); }); var demo = angular.module('demo'); demo.config(function (sampleProvider) { sampleProvider.setFoo(41); });
-
コンテンツのチラつきを防ぐため、
{{ }}
の代わりにng-bind
かng-cloak
を使いましょう。 -
テンプレートに複雑なコードを書くのは避けましょう。
-
イメージを動的に読み込むために
src
を使う必要がある場合はsrc
と{{ }}
を組み合わせて使う代わりにng-src
を使いましょう。 -
アンカータグの
href
の内容が動的な場合は、href
と{{ }}
を組み合わせて使う代わりにng-href
を使いましょう。 -
style
属性を付ける際に{{ }}
とともにscopeの変数を文字列として使う代わりに、ng-style
を利用することでオブジェクトのパラメータのように記述できます。また、scopeの変数も値として利用できます:<script> ... $scope.divStyle = { width: 200, position: 'relative' }; ... </script> <div ng-style="divStyle">my beautifully styled div which will work in IE</div>;
- ビューが表示される前に、
resolve
を使って依存関係の解決をしましょう。 resolve
コールバックの中に明示的なRESTfulの呼び出しはしないようにしましょう。全てのリクエストは適切なサービスに隠蔽します。この方法でキャッシュを使うことができますし、関心の分離の原則に則ることができます。
- バージョン1.4.0以降でビルトインのi18nツールを利用することができます。1.4.0より前のバージョンを利用している場合は、
angular-translate
を利用することができます。
-
digestサイクルの最適化
- 特に重要な変数に対してのみ監視を行います。
$digest
ループを明示的に記述する必要がある場合(例外的なケースだと思いますが)、本当に必要なときにのみ呼び出すようにします。(例えば、リアルタイム通信を使用する場合は、各受信メッセージ内で$digest
ループが発生しないようにします)。 - 初期化後に変更のないコンテンツを扱う場合、AngularJSの古いバージョンでは
bindonce
のようなシングルタイム・ワッチャーを使います。AngularJSのバージョン1.3.0以降では組み込みのワンタイム・バインディングを利用します。 $watch
内はできるだけシンプルな処理にします。一つの$watch
内で重くて遅い処理を作ってしまうとアプリケーション全体が遅くなってしまいます。(JavaScriptがシングルスレッドである性質上、$digest
のループはシングルスレッドで処理されます)。- コレクションを監視する場合、ほんとうに必要でなければオブジェクトの中身まで監視をするのはやめましょう。
$watchCollection
を用いて同等性の浅いレベルでの監視にとどめておくべきです。 $timeout
のコールバック関数が呼ばれることによって影響を受ける監視対象の変数がない場合に、$timeout
関数の3番目のパラメータをfalseにすることで$digest
ループをスキップします。- 巨大なコレクションを扱う場合、それはほとんど変更されません。不可変データ構造を利用しましょう。 -->
- 特に重要な変数に対してのみ監視を行います。
このスタイルガイドの目的はコミュニティ駆動であることです。ご協力いただけると大変ありがたいです。例えば、テストセクションを拡張することによって、または、このスタイルガイドをあなたの言語に翻訳することによってコミュニティに貢献することができます。
mgechev | morizotter | pascalockert | ericguirbal | yanivefraim | mainyaa |
elfinxx | agnislav | Xuefeng-Zhu | lukaszklis | previousdeveloper | susieyy |
rubystream | cironunes | cavarzan | guiltry | tornad | jmblog |
kuzzmi | dchest | clbn | apetro | valgreens | astalker |
bradgearon | dreame4 | gsamokovarov | grvcoelho | bargaorobalo | olov |
hermankan | jesselpalmer | capaj | johnnyghost | jordanyee | nacyot |
mariolamacchia | kirstein | mo-gr | cryptojuice | jabhishek | vorktanamobay |
sahat | kaneshin | imaimiami | thomastuts | grapswiz | coderhaoxin |
ntaoo | kuzmeig1 |