コンセプト概要

概要

AngularJSにはいくつかの概念が存在し、初めてのアプリケーションを作る前にそれを理解しておくことをお勧めします。 このセクションでは、シンプルな例を使用して素早くAngularJSの重要な部分について触れていきます。 ただし、全てを詳しく説明することは出来ません。 より詳しく知りたければ、 チュートリアルを確認してください。

概念 説明
テンプレート HTMLとそれに追加されたAngularJS独自のマークアップです。
ディレクティブ(Directive) カスタム属性と要素によってHTMLを拡張したものです。
モデル UI上でユーザーに表示するデータです。
スコープ ディレクティブと式からアクセス可能な、モデルが格納されたコンテキストです。
式(Expressions) スコープからアクセスする変数、または関数です。
コンパイラ テンプレートの解析、ディレクティブと式のインスタンス化を行います。
フィルター ユーザーに表示する式の値をフォーマットします。
ビュー ユーザーに表示するもの(DOM)です。
データバインディング モデルとビューの間のデータを同期する
コントローラー ビューを支えるビジネスロジックです。
依存性注入(DI) オブジェクト/関数の作成と取り付けを行います。
インジェクター 依存性注入のコンテナです。
モジュール インジェクターの構成を行います。
サービス ビューの独立した再利用可能なビジネスロジックです。

初めてのサンプル: データバインディング

下記は、請求書に掛かるコストを計算するフォーム構築の例です。 入力フィールドに数量とコストを入力し、請求書の合計金額を掛け合わせてみましょう。

<!doctype html>
<html ng-app>
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
  </head>
  <body>
    <div ng-init="qty=1;cost=2">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="cost" required >
      </div>
      <div>
        <b>Total:</b> {{qty * cost | currency}}
      </div>
    </div>
  </body>
</html>

上記のLiveプレビューを試したら、このサンプルを参考に学んでいきましょう。

これは、見慣れない新しいマークアップを含む通常のHTMLに見えます。 AngularJSでは、このようなファイルを"テンプレート(template)"と呼びます。 アプリケーション上でAngularJSの実行が開始されると、 テンプレートからこの新しいマークアップが"コンパイラ(compiler)"と呼ばれるものによって解析・処理されます。 読み込まれて、変形・描画されたDOMは、"ビュー(view)"と呼ばれます。

まず初めに学ぶ新しいマークアップは、"ディレクティブ(directive)"と呼ばれるものです。 ディレクティブは、HTMLの属性・要素に特別な振る舞いを適用します。 上記のサンプルでは、アプリケーションが初期化される際に、自動的にディレクティブとリンクするng-app属性が使用されています。 AngularJSはまた、input要素に対してディレクティブを定義して、この要素の振る舞いを拡張します。 (例. 自動的に入力されたテキストの検証を行うことが可能で、required属性で評価することでテキストの未入力を防ぎます) ng-modelディレクティブは、入力フィールド値を変数へ(から)、格納(更新)し、 CSSクラスを追加することによって入力フィールドに検証の状態を表示します。 上記のサンプルでは、この機能を利用して入力フィールドを空にすると罫線を赤くするようにしています。

Concepts databinding1

DOMへアクセスするためのカスタムディレクティブについて:
AngularJSでは、アプリケーションがDOMに触れられる唯一の場所はディレクティブ内です。 DOMへのアクセスはテストを困難にしてしまうため、これは良い慣習と言えます。 もし、DOMに直接アクセスする必要が有る場合は、そのためにカスタムディレクティブを書くべきです。 ディレクティブのガイドで、このことについて説明しています。

2つ目の新しいマークアップは、二重中括弧の{{ 式 | フィルター}}です。 コンパイラがこのマークアップを見つけると、このマークアップの値を評価して置換します。 テンプレート内の"式(expression)"は、JavaScriptのようなコードの断片で、変数の読み込み、書き込みが可能です。 ただし、これらの変数はグローバル空間の変数では無いことに注意してください。 AngularJSは、JavaScript関数内の変数として式にアクセス出来るようにしてくれる"スコープ(scope)"を提供します。 スコープの変数に格納されたこの値は、"モデル(model)"として参照されます。(モデルについては後述します) この2つ目の新しいマークアップは、上記のサンプルではAngularJSに対し、"入力欄からデータを取得し、それらの値を互いに掛け合わせよ"と指示しています。

また、このサンプルには"フィルター(filter)"も含まれています。 式の値をフィルターを通して、ユーザーのための表示にフォーマットします。 このサンプルでは、数値をcurrencyフォーマットで、金額に見えるように出力しています。

このサンプルで重要な事は、AngularJSがリアルタイムでデータの紐付け(ライブバインディング)を行っている事です。 入力の値が変更される度に、式の値は自動的に再計算され、DOMがその値に更新されます。 このコンセプトになっているのが、"双方向のデータバインディング(双方向データバインディング)"です。

UIロジックの追加: コントローラー

サンプルに更にロジックを加え、異なる通貨での入力と、 請求への支払いが出来るようにしましょう。

<!doctype html>
<html ng-app="invoice1">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
    <script src="invoice1.js"></script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>
angular.module('invoice1', [])
  .controller('InvoiceController', function() {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = ['USD', 'EUR', 'CNY'];
    this.usdToForeignRates = {
      USD: 1,
      EUR: 0.74,
      CNY: 6.09
    };

    this.total = function total(outCurr) {
      return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) {
      return amount * this.usdToForeignRates[outCurr] * 1 / this.usdToForeignRates[inCurr];
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  });
<!doctype html>
<html ng-app="invoice1">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script>angular.module('invoice1', [])
  .controller('InvoiceController', function() {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = ['USD', 'EUR', 'CNY'];
    this.usdToForeignRates = {
      USD: 1,
      EUR: 0.74,
      CNY: 6.09
    };

    this.total = function total(outCurr) {
      return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) {
      return amount * this.usdToForeignRates[outCurr] * 1 / this.usdToForeignRates[inCurr];
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  });
</script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>

何が変わったでしょうか?

まず、"コントローラー(controller)"と呼ばれるものを含む新しいJavaScriptファイルが増えました。 もう少し正確に言うと、このファイルは実際のコントローラーのインスタンスを作成するコンストラクタ関数を含みます。 コントローラーの目的は、変数と式の関数、ディレクティブが使用できるようにする事です。

コントローラーのコードを含む新しいファイルを加え、更にHTMLに対しng-controllerディレクティブの追加もしています。 このディレクティブはAngularJSに、InvoiceControllerがこの要素とその全ての子要素に対しての処理を請け負うという事を伝えます。 InvoiceController as invoiceの文は、Angularにコントローラーをインスタンス化し、 それを現在のスコープのinvoice変数に格納するように伝えています。

また、ページ内の全ての式にinvoice.プレフィックスを付けることによって、 コントローラーのインスタンス内の変数に対して読み込み、書き込みが出来るように変更しています。 選択可能な通貨は、コントローラー内で定義され、ng-repeatを使用してテンプレート内に追加されています。 コントローラーにはtotal関数が含まれており、{{ invoice.total(...) }}として、 DOMにその関数の結果を紐付けることが可能です。

再度、このサンプルのリアルタイムでのデータバインディングを確認してみましょう。 関数の結果が変更される度に、DOMが自動的に更新されます。 請求の「支払い」ボタンは、ngClickディレクティブを使用しています。 これによりボタンがクリックされる度に、指定されている式が評価されます。

また、新しいJavaScriptファイル内では、コントローラーを登録する新しいモジュールを作成しています。 モジュールについては、次のセクションで説明します。

下記の画像は、コントローラーの実装後にスコープとビュー各々が、互いにどのように動作しているのかを説明したものです。

Concepts databinding2

ビューのビジネスロジックからの独立: サービス

現在、このサンプルの全てのロジックはInvoiceControllerに含まれています。 アプリケーションが成長していく過程で、コントローラーからビューを、 "サービス(service)"と呼ばれる他のアプリケーションの一部として再利用可能なものに、 独立・分離させるのは良い慣習と言えます。 後ほど、コントローラーを変更せずに、WEBから為替レート(例:YahooファイナンスAPI)をサービスに読み込むようにアプリケーションを変更してみます。

それでは、サンプルをリファクタリングし、currency機能を別のファイルにサービスとして移動してみましょう。

<!doctype html>
<html ng-app="invoice2">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
    <script src="finance2.js"></script>
    <script src="invoice2.js"></script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>
angular.module('finance2', [])
  .factory('currencyConverter', function() {
    var currencies = ['USD', 'EUR', 'CNY'],
        usdToForeignRates = {
      USD: 1,
      EUR: 0.74,
      CNY: 6.09
    };
    return {
      currencies: currencies,
      convert: convert
    };

    function convert(amount, inCurr, outCurr) {
      return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
    }
  });
angular.module('invoice2', ['finance2'])
  .controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = currencyConverter.currencies;

    this.total = function total(outCurr) {
      return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  }]);
<!doctype html>
<html ng-app="invoice2">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script>angular.module('finance2', [])
  .factory('currencyConverter', function() {
    var currencies = ['USD', 'EUR', 'CNY'],
        usdToForeignRates = {
      USD: 1,
      EUR: 0.74,
      CNY: 6.09
    };
    return {
      currencies: currencies,
      convert: convert
    };

    function convert(amount, inCurr, outCurr) {
      return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
    }
  });
</script>
<script>angular.module('invoice2', ['finance2'])
  .controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = currencyConverter.currencies;

    this.total = function total(outCurr) {
      return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  }]);
</script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>

何が変わったでしょうか? convertCurrency関数と既存のcurrenciesの定義を、新しいfinance2.jsファイルに移動しましたが、 その分離した関数へ、コントローラーはどのようにしてアクセスすれば良いのでしょうか?

ここで、"依存性注入(DI)"の出番になります。 依存性注入(DI)は、オブジェクトと関数の作成とそれらの依存性の保持を取り扱うソフトウェア・デザインパターンです。 AngularJS内の全ての概念(ディレクティブ、フィルター、コントローラー、サービス、…)は、 依存性注入を使用して作成や構成が行われます。 AngularJSでは、DIコンテナのことを"インジェクター(injector)"と呼びます。

DIを使用するには、一緒に動作すべき全てのものが登録される場所が必要になります。 Angularでは、"モジュール(modules)"と呼ばれるものが、この役割を果たします。 Angularが開始されると、ng-appディレクティブによって名前が定義されたモジュールの、 このモジュールに依存するモジュール全てのものを含む構成が使用されます。

Concepts module service

上記のサンプルでは、テンプレートはng-app="invoice"のディレクティブを含んでいます。 これはAngularに、invoiceモジュールをアプリケーションの主モジュールとして使用するように指示しています。 コードのangular.module('invoice', ['finance'])の部分は、 invoiceモジュールがfinanceモジュールに依存することを指定しています。 これにより、AngularはInvoiceControllerと同様に、currencyConverterサービスも使用します。

これでAngularは、アプリケーションの構築に必要な全パーツを把握しました。 前のセクションで、コントローラーはファクトリー関数を使用して作成されることを説明しました。 サービスのための、ファクトリーの定義には複数の方法が存在します。(ガイドのサービス)を確認してください。 上記のサンプルでは、currencyConverter関数をサービスのファクトリーとして返す関数を使用しています。

最初の疑問に戻りますが、InvoiceControllerはどのようにしてcurrencyConverter関数の参照を取得するのでしょうか? Angularでは、これを単にコンストラクタ関数の引数での定義によって行います。 これにより、インジェクターが順番にオブジェクトを作成し、 前に作成したオブジェクトを、それらに依存するオブジェクトのファクトリーに渡すことが出来るようになります。 サンプルでは、InvoiceControllerはcurrencyConverterと名付けられた引数を持ちます。 このため、Angularはコントローラーとサービスの依存関係を知ることができ、 引数としてサービスインスタンスのコントローラーを呼び出します。

最後に、前のセクションのサンプルと、このセクションでのサンプルでの違いについてですが、 ここではmodule.controller関数に、プレーンな関数の代わりに配列を渡しています。 配列の1つ目にはコントローラーが必要とする依存サービスの名前を格納し、 配列の最後にはコントローラーのコンストラクタ関数を格納します。 Angularは、この配列の文法を使用して依存性を定義します。 これは、DIがコードの圧縮化の後でも動作するためで、多くの場合コードの圧縮化により、 コントローラーのコンストラクタ関数の引数が、aのような短いものに変更されてしまうためです。

バックエンドへのアクセス

YahooファイナンスAPIの為替レートを使用して、このサンプルを完成させましょう。 下記のサンプルで、この方法について確認してください。

<!doctype html>
<html ng-app="invoice3">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
    <script src="invoice3.js"></script>
    <script src="finance3.js"></script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>
angular.module('invoice3', ['finance3'])
  .controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = currencyConverter.currencies;

    this.total = function total(outCurr) {
      return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  }]);
angular.module('finance3', [])
  .factory('currencyConverter', ['$http', function($http) {
    var YAHOO_FINANCE_URL_PATTERN =
          'http://query.yahooapis.com/v1/public/yql?q=select * from '+
          'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
          'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK',
        currencies = ['USD', 'EUR', 'CNY'],
        usdToForeignRates = {};
    refresh();
    return {
      currencies: currencies,
      convert: convert,
      refresh: refresh
    };

    function convert(amount, inCurr, outCurr) {
      return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
    }

    function refresh() {
      var url = YAHOO_FINANCE_URL_PATTERN.
                 replace('PAIRS', 'USD' + currencies.join('","USD'));
      return $http.jsonp(url).success(function(data) {
        var newUsdToForeignRates = {};
        angular.forEach(data.query.results.rate, function(rate) {
          var currency = rate.id.substring(3,6);
          newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
        });
        usdToForeignRates = newUsdToForeignRates;
      });
    }
  }]);
<!doctype html>
<html ng-app="invoice3">
  <head>
    <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script>angular.module('invoice3', ['finance3'])
  .controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
    this.qty = 1;
    this.cost = 2;
    this.inCurr = 'EUR';
    this.currencies = currencyConverter.currencies;

    this.total = function total(outCurr) {
      return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
    };
    this.pay = function pay() {
      window.alert("Thanks!");
    };
  }]);
</script>
<script>angular.module('finance3', [])
  .factory('currencyConverter', ['$http', function($http) {
    var YAHOO_FINANCE_URL_PATTERN =
          'http://query.yahooapis.com/v1/public/yql?q=select * from '+
          'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
          'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK',
        currencies = ['USD', 'EUR', 'CNY'],
        usdToForeignRates = {};
    refresh();
    return {
      currencies: currencies,
      convert: convert,
      refresh: refresh
    };

    function convert(amount, inCurr, outCurr) {
      return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
    }

    function refresh() {
      var url = YAHOO_FINANCE_URL_PATTERN.
                 replace('PAIRS', 'USD' + currencies.join('","USD'));
      return $http.jsonp(url).success(function(data) {
        var newUsdToForeignRates = {};
        angular.forEach(data.query.results.rate, function(rate) {
          var currency = rate.id.substring(3,6);
          newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
        });
        usdToForeignRates = newUsdToForeignRates;
      });
    }
  }]);
</script>
  </head>
  <body>
    <div ng-controller="InvoiceController as invoice">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="invoice.qty" required >
      </div>
      <div>
        Costs: <input type="number" ng-model="invoice.cost" required >
        <select ng-model="invoice.inCurr">
          <option ng-repeat="c in invoice.currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in invoice.currencies">
          {{invoice.total(c) | currency:c}}
        </span>
        <button class="btn" ng-click="invoice.pay()">Pay</button>
      </div>
    </div>
  </body>
</html>

何が変わったでしょうか? financeモジュールのcurrencyConverterサービスが、バックエンド(サーバ)にアクセスするために、 AngularJSから提供される組み込みの$httpサービスを使用しています。 これはXMLHttpRequestと、 JSONPトランスポート周りをラップします。 詳細については、APIドキュメントを確認してください。

 Back to top

© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.

このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。