はじめに

July 11, 2016 · View on GitHub

Join the chat at https://gitter.im/mgechev/angularjs-style-guide

はじめに

このスタイルガイドの目的はAngularJSアプリケーションのベストプラクティスとスタイルガイドラインを提供することです。 これらのベストプラクティスは以下から集めたものです:

  1. AngularJSソースコード
  2. 私が読んだコードや文章
  3. 私の経験

注意1: このスタイルガイドは草稿であり、その主な目的はコミュニティ駆動にすることです。足りない部分を補うことはコミュニティ全体から大きな賞賛を受けることになります。

注意2: 翻訳版のガイドラインを読み始める前に、それが最新の状態であるか確認しましょう。英語版のAngularJSスタイルガイドが最新版となります。

当ガイドラインは、JavaScript開発のガイドラインではありません。JavaScript開発のガイドラインはこちらで見つけることができます:

  1. Google JavaScript スタイルガイド
  2. Mozilla JavaScript スタイルガイド
  3. Douglas Crockford JavaScript スタイルガイド
  4. Airbnb JavaScript スタイルガイド

AngularJSの開発をする上でのおすすめはGoogle JavaScript スタイルガイドです。

AngularJSのGitHub WikiにProLoserの書いた類似のセクションがあります。こちらで確認することができます。

Translations

目次

全般

ディレクトリ構造

規模の大きな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つのコンポーネントのみがあるようにします。ファイル名にはコンポーネント名を付けます。
  • Yeomanng-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.$applysetTimeout 内に書き忘れる)。

  • 以下のツールを使用してワークフローを自動化しましょう:

  • コールバックの代わりにpromise( $q )を使います。コードはよりエレガントですっきりとしますし、コールバック地獄から解放されます。

  • できるだけ $http の代わりに $resource を使います。抽象性を高めることにより冗長なコードから解放されます。

  • AngularJS pre-minifier(ng-annotate)を使うことで、minifyした後に発生する問題を回避しましょう。

  • グローバル変数を使用してはいけません。依存性の注入を使って全ての依存関係を解決することで、バグやテスト時のモンキーパッチを防ぎます。

  • GruntやGulpを使ってコードをIIFE(Immediately Invoked Function Expression)にラップすることによってglobalを使わないようにしましょう。プラグインとしてはgrunt-wrapgulp-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つあります:

    1. 機能性
    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のソースコードでも使用されています。
  • サードパーティ製ライブラリとの名前空間の衝突を防ぐために、新たに作成するディレクティブ名にはプレフィックスを付けましょう。
  • ngui などのプレフィックスは使わないようにしましょう。これらは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-bindng-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の呼び出しはしないようにしましょう。全てのリクエストは適切なサービスに隠蔽します。この方法でキャッシュを使うことができますし、関心の分離の原則に則ることができます。

i18n

  • バージョン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 ループをスキップします。
    • 巨大なコレクションを扱う場合、それはほとんど変更されません。不可変データ構造を利用しましょう。 -->

コントリビューション

このスタイルガイドの目的はコミュニティ駆動であることです。ご協力いただけると大変ありがたいです。例えば、テストセクションを拡張することによって、または、このスタイルガイドをあなたの言語に翻訳することによってコミュニティに貢献することができます。

Contributors

mgechevmorizotterpascalockertericguirbalyanivefraimmainyaa
mgechevmorizotterpascalockertericguirbalyanivefraimmainyaa
elfinxxagnislavXuefeng-Zhulukaszklispreviousdevelopersusieyy
elfinxxagnislavXuefeng-Zhulukaszklispreviousdevelopersusieyy
rubystreamcironunescavarzanguiltrytornadjmblog
rubystreamcironunescavarzanguiltrytornadjmblog
kuzzmidchestclbnapetrovalgreensastalker
kuzzmidchestclbnapetrovalgreensastalker
bradgearondreame4gsamokovarovgrvcoelhobargaorobaloolov
bradgearondreame4gsamokovarovgrvcoelhobargaorobaloolov
hermankanjesselpalmercapajjohnnyghostjordanyeenacyot
hermankanjesselpalmercapajjohnnyghostjordanyeenacyot
mariolamacchiakirsteinmo-grcryptojuicejabhishekvorktanamobay
mariolamacchiakirsteinmo-grcryptojuicejabhishekvorktanamobay
sahatkaneshinimaimiamithomastutsgrapswizcoderhaoxin
sahatkaneshinimaimiamithomastutsgrapswizcoderhaoxin
ntaookuzmeig1
ntaookuzmeig1