前のページ

次のページ


Angularのアーキテクチャ概説



Angularアプリケーションの基本的な構成要素

AngularHTMLJavaScriptに変換される言語(DartTypeScript)でクライアント・アプリケーションを構築するのを支援するフレームワークです。

DartのためのAngularangular2パッケージとして提供されており、これは(他の多くの言語と同様)Pubツールを使って利用できます。

Angularを使った場合、Angular化されたマークアップでHTMLテンプレート (HTML templates) を組みたててアプリケーションを書き、これらのテンプレートを扱うためのコンポネント・クラス (component classes) を書き、サービス (services) のなかにアプリケーション・ロジックを付加し、これらのコンポネントとサービスをモジュール (modules) とします。

このアプリケーションを走らせるときはトップのルート・モジュールAngularブートストラッパ (bootstrapper) に渡します。Angularはそれを引き受け、我々が用意した指示に従ってブラウザにそのアプリケーションの内容を表示し、ユーザの関わり合いに応答します。

むろんこれ以外に多くの事柄がありますが、それらの詳細はこのガイドの章の後の節の中で学ぶことになります。



このアーキテクチャ図はAngularアプリケーションの8つの主たる構成要素を示しています:

  1. モジュール (Modules)

  2. コンポネント (Components)

  3. テンプレート (Templates)

  4. メタデータ (Metadata)

  5. データ・バインド (Data binding)

  6. ディレクティブ (Directives)

  7. サービス (Services)

  8. 依存物注入 (Dependency injection)

これらを順に説明していきます。

なお本章で参照しているコードはライブのサンプル(ソース・コードを見る)として利用できます。



モジュール(Modules)

Angularのアプリケーションはモジ ュラー構成になっています。

一般的にあるアプリケーションは多くのモジュール (module) で構成されています。

典型的なモジュールは単一の目的のために特化したコードのかたまりです。モジュールはその中の値の何か(典型的にはクラスといった)を外部から使えるようエクスポート (export) します(使う側は使いたいモジュールをインポート (import)します)。(訳者注:この節は完全にDart用に直されていません。exportTypeScriptのキーワードです。Dartではアンダスコア(_)が最初に付く識別子を持ったオブジェクト以外はインポートすれば外部から可視になります)



Dartで違っているところ

この解説では、モジュールという用語はライブラリあるいはパッケージといったDartのコンパイル単位のことを言います(もしDartファイルがlibrary またはpart ディレクティブを持っていないとしたら、そのファイル自身がライブラリであり、従ってコンパイル単位 (compilation unit) だということになります)。コンパイル単位の詳細は「Dart言語仕様書」または「プログラミング言語Dartの基礎」の「ライブラリとスクリプト」の章を見てください。

多分読者が最初に出くわすモジュールはあるコンポネントのクラスモジュールです。このコンポネントは基本的なAngularのブロックのひとつであり、多くのコンポネントをこれから書くことになり、次の節でコンポネントに関し解説します。ここではコンポネント・クラスはあるモジュールとしてインポートされるたぐいのものだということを知っておれば十分です。

殆どのアプリケーションはひとつのAppComponent を持っています。これはそのアプリケーションのトップ・レベルのコンポネントで、慣例としてapp_component.dartという名前のファイルが使われています。そのようなファイルの中身を見ると、次のような宣言があることが判ります。

lib/app_component.dart (export)

class AppComponent { }



Dartで違っているところ

TypeScriptと違って、Dartのライブラリはそのパブリック (public) な名前空間のすべての名前と宣言を常にエクスポートしており、明示的なエクスポート修飾詞は不必要です。

あるモジュールがある宣言をエクスポートしているということは、その宣言がパブリックだということを意味します。名前空間に関するより詳細は「Dart言語仕様書」または「プログラミング言語Dartの基礎」の「エクスポート」の記述を読んでください。

AppComponentへの参照が必要な場合は、次のようにインポートします:

web/main.dart (import)

import 'package:developer_guide_intro/app_component.dart';



Angularライブラリ (Angular libraries)



Angularangular2パッケージの中のライブラリの集まりとして提供されています。

Angularライブラリの名前は先頭に angular2が付きます。

angular2/core は主要なAngularライブラリで、そこから必要とする殆どを得ることになります。

Angularライブラリには他にもangular2/common angular2/router といったような重要なライブラリがあります。我々はあるAngularライブラリから必要とするものを次に示すようにインポートすることになります。

lib/app_component.dart (import)

import 'package:angular2/core.dart';

ここでのポイントは:

  • Angularアプリケーションはモジュールたちで構成されています。

  • モジュールたちはクラス、関数、変数といったものをライブラリやパッケージとしたもので、それを他のモジュールたちがインポートします。

  • 自分のアプリケーションをモジュールたちの集まりとして書き、他のモジュールたちが各モジュールをインポートするようにすることをお勧めします。

おそらくは我々が書く最初のモジュールは、あるコンポネントを宣言するものになります。


コンポネント (components)

コンポネントはビュー (view) と呼ぶスクリーン上の実際の置き場所への貼り付けを制御します。この章のアプリケーションの核になるものはナビゲーションのリンク、英雄たち(heroes)のリスト、heroのエディタ等々で、これらのビューの総てがコンポネントたちで制御されています。

あるコンポネントのアプリケーション・ロジック(そのビューをサポートすること)はあるクラス内で定義します。このクラスはプロパティとメソッドのAPIを介してそのビューとかかわりあいます。

例えばHeroListComponentはあるサービスで取得したヒーローたち(heroes)のリストを返す heroesというプロパティを有します。selectHero()メソッドはユーザがそのリストからあるヒーローを選択するのにクリックしたときに selectedHeroという属性をセットします。その場合のコンポネントは次のようなクラスになります:

lib/hero_list_component.dart (class)

class HeroListComponent implements OnInit {
  List<Hero> heroes;
  Hero selectedHero;
  final HeroService _heroService;

  HeroListComponent(this._heroService);

  void ngOnInit() {
    heroes = _heroService.getHeroes();
  }

  void selectHero(Hero hero) {
    selectedHero = hero;
  }
}



Angularはユーザがそのアプリケーション内でかかわりあう中でコンポネントたちを生成、更新、及び削除します。アプリケーション開発者はオプショナルなライフサイクル・フック (lifecycle hooks)を介して、上のコードのなかで宣言されているngOnInit()のように、このライフサイクルの各状態でのアクションを用意することができます。

このコンポネントのコンストラクタを誰が呼び出しているのか?あるいは誰がこのサービス・パラメタを用意しているのか気になるかと思います。当面は必要になったときにAngularがこのコンストラクタを呼び、しかるべき HeroServiceを提供するのだと信じてください。


テンプレート (Templates)



我々はあるコンポネントのビューを、コンポネントと対になるテンプレートで定義します。テンプレートはHTMLの形式で、Angularに対しそのコンポネントをどのように表示するのかを知らせます。



テンプレートは大抵通常のHTMLのように見えますがちょっと変わったものになっています。以下はHeroListComponentのテンプレートです:


lib/hero_list_component.html

<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>

<hero-detail *ngIf="selectedHero != null" [hero]="selectedHero"></hero-detail>

このテンプレートは<h2><p>のようなHTML要素が使われています。しかし*ngFor, {{hero.name}}, (click), [hero], 及び<hero-detail>はなんでしょうか?

これはAngularテンプレート構文 (template syntax)の例です。読者はそのうちこの構文に慣れ、好きになると思います。まずこれの説明から始めます。

その前に最後の行に注目願います。<hero-detail>タグはHeroDetailComponentを表現しているカスタム要素です。

このHeroDetailComponentは我々が調べてきたHeroListComponentとは別のコンポネントです。HeroDetailComponentは(コードは示してありません)ユーザがHeroListComponentで表示されたリストから選択した特定のヒーローに関する内容を表示するものです。HeroDetailComponentHeroListComponentチャイルドです。



<hero-detail>来のHTML要素たちとうまく適合していることに注意してください。これからは同じレイアウトのなかで本来のHTMLと自分たちのカスタムの要素たちをミックスさせてゆくことになります。

このように、自分たちの特徴的なアプリケーションを構築するのに複雑なコンポネントのツリーを組み立ててゆきます。





テンプレートはコンポネントと対になるものですが、なにもDartコードの'.dart'ファイルと別の'.html'ファイルに置かれる必要はありません。次に説明する@Componentアノテーションのなかのtemplateプロパティとして.dartファイルの中に記述することも可能です。我々のアプリケーションのsales_tax_component.dartがその例です。



メタデータ (Metadata)

メタデータはAngularに対しあるクラスをどのように処理するかを知らせます。

先ほどのHeroListComponentのコードをみると、単一のクラスのみが存在しています。このクラス内にはフレームワークが使われているという証拠はなく、”Angular”という字句もありません。

実際のところこれは単にクラスです。Angularに対しそれを知らせるまではコンポネントではありません。

Angularに対しHeroListComponentがコンポネントであることは、そのクラスに対しメタデータ (metadata)を付すことで知らせます。

Dartではアノテーション (annotation)を使ってメタデータを付します。以下はHeroListComponentに対する幾つかのメタデータです:

lib/hero_list_component.dart (metadata)

@Component(
    selector: 'hero-list',
    templateUrl: 'hero_list_component.html',
    directives: const [HeroDetailComponent],
    providers: const [HeroService]
    )
class HeroListComponent implements OnInit {
/* . . . */
}

ここでは@Componentアノテーションがあり、直下のクラスがコンポネント・クラスであることを指定します。

アノテーションは設定パラメタたちを持ちます。@ComponentアノテーションはAngularがそのコンポネントとビューを生成し表示するのに必要な情報を与えるためにパラメタたちを使います。

以下は@Componentパラメタたちの幾つかを示してあります:

  • selector: 親のHTMLの中の<hero-list>タグが見つかったところにこのコンポネントのインスタンスを生成し挿入するようAngularに知らせるCSSセレクタです。例えばあるアプリケーションのHTML<hero-list></hero-list>を含んでいた時は、Angularはこれらのタグ間にHeroListComponentのインスタンスのビューを挿入します。

  • templateUrl: ひとつ前に示したこのコンポネントのテンプレートの場所です。

  • Directives: このテンプレートが必要とするコンポネントたちまたはディレクティブたちのリスト。上のテンプレートでは<hero-detail>タグ間で示された場所にAngularHeroDetailComponentを挿入することを想定しています。Angularはこのdirectivesリストの中でHeroDetailComponentが記されている場合に限り想定された通り処理します。

  • providers: このコンポネントが必要とするサービスたちのための依存性注入プロバイダ (dependency injection probiders)のリストです。自分たちのコンポネントのコンストラクタがを必要としているかを知らせるひとつの手段です。これにより表示するためのヒーローたちのリストを取得できます。依存性注入に関しては後で説明します。



実行時にAngular@Componentアノテーションで指定されたメタデータを読み出します。これによりAngularは「ちゃんとしたこと」ができる方法を知ることができます。

テンプレート、メタデータ、及びコンポネントが一緒になってあるビューを記述しています。

Angularの振る舞いを指定するのに他のメタデータ・アノテーションたちを同じやり方で適用します。@Injectable@Input, 及び @Outputは最も一般的なアノテーションの幾つかで、これらはAngularの知識が深まるにつれマスターすることになります。

かいつまんでいえばAngular何をするかを知るために自分たちのコードにメタデータを付加しなければならないということです。








データのバインド (Data binding)

フレームワークなしだと、HTMLコントロールにデータの値を入れ込み、ユーザの応答をアクションと値の更新に変えることを自分でやらねばなりません。そのようなプッシュプルのロジックを手作業で書くのは大変であり、エラーを起こしがちであり、読むのも大変です。これは経験を積んだ jQuery のプログラマたちが納得できることです。



Angularはデータ・バインドに対応しております。これはテンプレートのパーツたちとコンポネントのパーツたちを連携させるメカニズムです。我々はHTMLのテンプレートにバインドのためのマークアップを付加してAngularに対して双方をどのように結びつけるかを知らせます。





データ・バインドの構文には4つの形式があります。各形式にはこの図で矢印で示されているように方向(DOMへ、DOMから、あるいは双方向で)があります。

ここで使っているサンプルのテンプレートでは3つの形式が使われています:

lib/hero_list_component.html (binding)

<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>
  • {{hero.name}}内挿 (interpolation)はこのコンポネントのhero.nameプロパティの値を<li>タグ間に表示します。

  • [hero]プロパティ・インド (property binding)は親のHeroListComponentselectedHeroの値を子のHeroDetailComponentheroプロパティに渡します。

  • (click)イベント・バインド (event binding)はユーザがあるヒーローの名前をクリックしたときにこのコンポネントのselectHeroメソッドを呼び出します。

双方向データ・バインド(Two-way data binding)4番目の重要な形式で、ngModel指令を使って単一の記述でプロパティとイベントのバインドを組み合わせたものです。HeroListComponentテンプレートには双方向バインドはありませんが、以下にHeroDetailComponentテンプレートからの例を示します:

lib/hero_detail_component.html (ngModel)

<input [(ngModel)]="hero.name">

双方向バインドではデータ・プロパティ値はプロパティ・バインド同様コンポネントから入力ボックスに流れます。ユーザによる変更はまたコンポネントに逆流し、イベント・バインドのようにプロパティを最新の値にセットします。

AngularJavaScriptのイベント・サイクルあたり一回総てのデータ・バインドをそのアプリケーションのコンポネント・ツリーのルートから枝葉にわたって処理します。

詳細のすべては未だ解っていなくても、これらの例をみればデータ・バインドはテンプレートとコンポネント間の通信の中で重要な役割を果たしていることがはっきりすると思います。



データ・バインドはまた親と子のコンポネント間の通信にとっても重要です。






ディレクティブ (Directives)



Angularのテンプレートは動的です。Angularがこれらを描写するときに、ディレクティブで与えられた指示に従ってそのDOMを変換します。

ディレクティブはディレクティブ・メタデータを持ったクラスです。Dartではそのクラスにメタデータを付すときは@Directiveアノテーションを適用します。



我々は既にコンポネントのディレクティブ形式を見てきています。コンポネントはテンプレートつきのディレクティブです; 実際@Componentアノテーションはテンプレートがもとになっている機能たちで拡張した@Directiveアノテーションです。

コンポネントは技術的にはディレクティブではありますが、コンポネントはAngularアプリケーションにとっては特に特徴的かつ核になっているので、このアーキテクチャ概説ではコンポネントとディレクティブを分けて説明しています。

ディレクティブにはその他の2つの形式が存在します:即ち構造 (structual)と属性(attribute)ディレクティブです。

これらのディレクティブは属性と同様要素タグの内側に、時には名前であるいはより多くは代入またはバインドのターゲットとして置かれます。

構造ディレクティブDOMのなかの要素を付加、削除、および置き換えることでレイアウトを変えます。

今使っているサンプルでは2つの組み込み構造ディレクティブが使われています:

hero_list_component.html (structural)

<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero != null"></hero-detail>
  • *ngForAngularに対しheroesリストの中でheroあたりひとつの<li>を取り出すことを告げます。

  • *ngIfは選択されたheroが存在するときに限りコンポネントを含めます。

Dartで違っているところ

Dartでは、真である値のみがブール値はtrueです;他のすべての値はfalseです。これに対しJavaScriptTypeScriptでは1といった値やnullでないオブジェクトはtrueとして扱われます。その為、このアプリケーションのJavaScriptTypeScriptのバージョンでは*ngIfの値として単にselectedHeroが使えます。Dartバージョンでは!= といったブール演算子を使わねばなりません。

属性ディレクティブは既存のエレメントの見かけや振る舞いを変更します。テンプレートの中ではこれらは通常のHTML属性(従って名前)のように見えます。

双方向バインドを実装しているngModelディレクティブは属性ディレクティブの一例です。ngModelはその表示値属性をセットし変更イベントに応答することで既存の要素(通常<input>)の振る舞いを変更します。

lib/hero_detail_component.html (ngModel)

<input [(ngModel)]="hero.name">

Angularにはレイアウト構造を変更する(例えばngSwitch)またはDOM要素とコンポネントの様相を変える(例えばngStyle ngClass)のどちらかを指定する幾つかのディレクティブが用意されています。

無論、自分自身のディレクティブを書くことも可能です。HeroListComponentのようなコンポネントはカスタムのディレクティブの一種です。


サービス (Service)



サービスは自分のアプリケーションが必要とする値、関数、あるいは機能のいずれかをカバーする幅広いカテゴリです。

殆どなんでもサービスになり得ます。一般的にはサービスは狭く、はっきりした目的を持ったクラスです。



例としては以下のようなものが挙げられます:

ログ・サービス(logging service

データ・サービス(data service

メッセージ・バス(message bus)

税金の計算(tax calculator)

アプリケーション設定(application configuration)

サービスに関してはAngularの限ったものはありません。Angular自身サービスに関する定義は持っていません。サービス・ベースのクラスはなく、サービスを登録する場所もありません。

とは言えAngularアプリケーションにとってサービスは不可欠です。

以下はブラウザのコンソールにログを表示するサービス・クラスの例です:

lib/logger_service.dart (class)

class Logger {
  void log(Object msg) => window.console.log(msg);
  void error(Object msg) => window.console.error(msg);
  void warn(Object msg) => window.console.warn(msg);
}

以下はheroesを取ってきてFutureの終了のなかでそれらを返すHeroServiceです。

このHeroServiceLoggerサービスと、もう一つのサーバ通信の仕事を取り扱うBackendServiceに依存しています。

lib/hero_service.dart (class)

class HeroService {
  final BackendService _backendService;
  final Logger _logger;
  final List<Hero> heroes = [];

  HeroService(this._logger, this._backendService);

  List<Hero> getHeroes() {
    _backendService.getAll(Hero).then((heroes) {
      _logger.log('Fetched ${heroes.length} heroes.');
      this.heroes.addAll(heroes); // fill cache
    });
    return heroes;
  }
}

サービスはどこにも存在します。

我々が作成するコンポネント・クラスはなるべく簡素なほうが好まれます。我々が作成するコンポネントはサーバからデータを取ってくることはせず、ユーザ入力を検証することもせず、またコンソールに直接ログを出力することもしません。そのようなタスクはサービスに委譲します。

あるコンポネントの仕事はユーザ経験を可能とすることで、それ以上ではありません。コンポネントはビュー(テンプレートによって描写される)とアプリケーション・ロジック(しばしばモデルの概念を持つものが含まれる)との間を仲介するものです。重要なものは総てサービスに委譲します。

Angularはこれらの原則を強いてはいません3000行にもなるいっぱい詰まったコンポネントを書いたとてもAngularは文句を言いません。

Angularは自分たちのアプリケーション・ロジックをサービスたちに分解し易くし、それらのサービスを依存性注入(dependency injection)を介してコンポネントが使い易くすることで、これらの原則に従い易くしています。


依存注入 (Dependency injection)

依存物注入はあるクラスの新規インスタンスをそれが必要とする完全なかたちの依存物たちを持たせて供給する手段です。殆どの依存物はサービスになります。Angularは依存物注入を使ってそれらが必要とするサービスを持った新規のコンポネントを用意します。

Angularはコンストラクタのパラメタの型を見てそのコンポネントがどのサービスを必要としているかを知らせることができます。例えば、ここで使っているサンプルのHeroListComponentHeroServiceを必要とします:

lib/hero_list_component.dart (constructor)

final HeroService _heroService;

HeroListComponent(this._heroService);

Angularがあるコンポネントを生成するとき、最初にインジェクタ (injector)にたいしそのコンポネントが必要なサービスたちを要求します。

インジェクタは自分が既に生成したサービス・インスタンスのコンテナを持っています。要求されたサービスのインスタンスがコンテナ内になかった場合は、インジェクタはそれを生成し、Angularにそのサービスを返す前にそれをコンテナに追加します。要求されたサービスのすべてが生成され、返されたら、Angularはそのコンポネントのコンストラクタをこれらのサービスを引数として呼び出すことが可能となります。これが依存物注入の意味する事柄です。

HeroService の注入はこんな形になります:



もしインジェクタがHeroService を持っていなかったら、どうやってそれを生成するかが判るのでしょうか?

簡単に言えばHeroServiceのプロバイダ (供給者:providers)を前もってinjectorに登録しておかねばなりません。プロバイダはあるサービスを生成あるいは返すことが可能なものであり、一般的にはそのサービスのクラス自身になります。

アプリケーションのコンポネントのツリーのどのレベルにおいてもプロバイダを登録できます。しばしばそれはそのアプリケーションのブート時にルートでなされ、そのサービスの同じインスタンスがどこからでも使えるようにします。

web/main.dart (bootstrap)

bootstrap(AppComponent, [BackendService, HeroService, Logger]);

代替手段として、コンポネントのレベルで、@Componentメタデータのproviders属性のなかで登録できます。

lib/hero_list_component.dart (providers)

providers: const [HeroService]

あるコンポネントのレベルで登録するというもとは、そのコンポネントの各新規インスタンスを持ったそのサービスの新規インスタンスを取得するということです。

依存物注入のポイントは次のようになります:

  • インジェクタが主たるメカニズムです。

  • インジェクタはそれが生成したサービス・インスタンスたちのコンテナを維持します。

    • インジェクタはプロバイダから新規サービス・インスタンスを生成できます。

    • プロバイダはサービス生成のための秘訣です

  • プロバイダたちをインジェクタに登録します。


まとめ

ここまでAngularアプリケーションの8つの主たる組み立てブロックに関してやや詳しく学習してきました:

  1. モジュール (Modules)

  2. コンポネント (Components)

  3. テンプレート (Templates)

  4. メタデータ (Metadata)

  5. データ・バインド (Data binding)

  6. ディレクティブ (Directives)

  7. サービス (Services)

  8. 依存物注入 (Dependency injection)

これがAngularアプリケーションのなかで何をおいても基本となり、学習を進めるには十分です。しかしこれは必要とするあるいは知りたくなる総てが含まれているわけではありません。

以下にアルファベット順にその他の重要なAngularの機能とサービスを挙げておきます。これらの殆どはこのデベロッパーズ・ガイドの中で解説される予定です。



アニメーション(Animation): アニメーションのライブラリは、アニメーションの技術やCSSの深い知識がなくてもコンポネントの振る舞いを動画化できます。

ブートストラップ(Bootstrap): ルートのアプリケーション・コンポネントを設定及び起動させるメソッドです。

変化検出(Change detection): あるコンポネントのプロパティ値が変化したか、及びいつスクリーンを更新するかをどのようにAngularが判断するかを学びます。非同期動作を押さえ、その変化検出戦略を実行させるのにzoneをどのように使うかを学びます。

コンポネント・ルータ(Component router: コンポネント・ルータのサービスを使うと、ユーザはマルチ・スクリーンのアプリケーションを馴染みのあるURLを使ったウェブ・ブラウズのスタイルでナビゲートできます。

イベント(Events): DOMがイベントを生起します。従ってコンポネントとサービスもイベントを生起します。Angularはイベントのパブリッシとイベントへの加入(subscribing)のメカニズムを提供します。

フォーム(Forms): HTMLベースの検証とダーティ・チェッキング(dirty checking)を持った複雑なデータ・エントリのシナリオに対応します。

HTTP: データ取得、データ保存のためにサーバと通信し、HTTPクライアントでサーバ・サイドのアクションを呼び出します。

ライフサイクル・フック(Lifecycle hooks): ライフサイクル・フックを実装することであるコンポネントの生成から削除に至る主要な瞬間に関与できます。

パイプ(Pipes): 表示のための値を変換するサービスです。ユーザ経験を改善するためにテンプレートにパイプを置くことができます。この通貨パイプ式を考えてみましょう:

price | currency:'USD':true

これは"42.33"という値を$42.33と表示します。

ルータ(Router): そのクライアント・アプリケーションのなかでページからページへナビゲートし、ブラウザから出てしまうことはありません。

テスト(Testing): Angularにはテストのライブラリが用意されており、Angularと関わりあいながらアプリケーションの部品上でユニット・テストができます。


コード・ウォークスルー (Code Walkthrough)


以下は読者がこの解説で使われているarchitectureアプリケーションIDE上で実際に眺めかつ触れてみることで、上記の構成要素たちを理解してもらうよう訳者が追加しました。

Angularをより深く理解するには、本ガイドの残りの章を読んでいただく必要があります。

IDEとしてIntelliJを使う場合は「AngularDartの開発環境をつくる」の章、または「プログラミング言語Dartの基礎」16.2節のなかの「AngularDartのパッケージ」を参照願います。


architectureアプリのモジュール構成

最初に「クイック・スタート」の章の「IntelliJでこのアプリを開いて見てみよう」の節に従って、このアプリケーションIDE上で開いてみましょう。

このアプリケーション(Pub get実行後)をIDEのプロジェクト・ビューでみると次のような構成になっていることが判ります。

総てのAngularアプリケーションは2つのフォルダで構成されています。



  • web:このフォルダの中の3つのファイルはこのアプリケーションの入口とも言え、ブラウザが直接アクセスします。但しDartVMが実装されていない通常のブラウザに対しては、Pub buildmain.dartJavaScript変換したmain.dart.jsを用意します。

  • lib:拡張子が.dartのファイルはいわゆるライブラリで、他のDartコードによってインポートされます。ここにはサービスの.dartファイル、コンポネントの.dartファイル、これと対になるコンポネントの.htmlファイル、その他のDartライブラリ・ファイルが置かれます。

アプリケーション開発者はこれらのフォルダ内に必要なコードを収容します。この例で分かるように、自分のアプリケーションをライブラリたちの集まりとして書くので、フォルダlibには多くのファイルが含まれることになります。大きなアプリケーションでは更にサブフォルダを用意して、より構造化します。


architectureアプリのライブラリ

フォルダlibの中は以下の12個のファイルからなる構成となっています:

コンポネント

Dartコード

テンプレート

依存するサービス

app_component.dart

@Componentアノテーションの中のtemplate:プロパティとして


トップ・レベルのコンポネント

hero_list_component.dart

hero_list_component.html

HeroService

app.componentの子のルート・レベル

sales_tax_component.dart

@Componentアノテーションの中のtemplate:プロパティとして

SalesTaxService

TaxRateService

app.componentの子のルート・レベル

hero_detail_component.dart

hero_detail_component.html


hero_list_componentの子のレベル



サービス

Dartコード

クラス

backend_service.dart

BackendService

HeroServiceのコンストラクタで呼ばれる

helo_service.dart

HeroService

ヒーロたちのリストを取得するメソッドgetHeroesを持つサービスでBackendServiceLoggerに依存

sales_tax_service.dart

SalesTaxService

Stringまたはnumのオブジェクト売上税金額を得るメソッドgetVATを持つサービスでTaxRateServiceに依存

tax_rate_service.dart

TaxRateService

税率を取得するメソッドgetRateを持つサービス

logger_service.dart

Logger

ブラウザのコンソールにログ、エラー、警告を出力するメソッドたちを有するクラス



その他

Dartコード

クラス

Hero.dart

Hero

Heroのクラス定義



architectureアプリのコンポネント

AngularのアプリケーションはAppComponentをトップとしたコンポネントたちのトリーで構成されます。architectureアプリケーションも4つのコンポネントで構成されています:




ビュー上では下図のように3つのコンポネントで構成されています:


  • hero_list_componentはヒーローたちのリストを表示し、またユーザのクリックによりhero_detail_componentによる表示を付加します

  • hero_detail_componenthero_list_componentの子で、原文では「heroエディタ」と称しています。まず上のリストのどれかをユーザがクリックするとその詳細が表示されます。次にNamePowerはユーザが変更できます。その変更は上のリストにも反映されます

  • sales_tax_componentは上のコンポネントとは独立しています。ユーザがAmount(金額)を数字で入力すると売上税額が表示されます

例えば、helo_list_componentを見てみましょう。このコンポネントのDartコードは次のようになっています:

lib/hero_list_component.dart

import 'package:angular2/core.dart';
import 'hero.dart';
import 'hero_detail_component.dart';
import 'hero_service.dart';
@Component(
    selector: 'hero-list',
    templateUrl: 'hero_list_component.html',
    directives: const [HeroDetailComponent],
    providers: const [HeroService])
class HeroListComponent implements OnInit {
  List<Hero> heroes;
  Hero selectedHero;
  final HeroService _heroService;
  HeroListComponent(this._heroService);
  void ngOnInit() {
    heroes = _heroService.getHeroes();
  }
  void selectHero(Hero hero) {
    selectedHero = hero;
  }
}

コンポネントのところで説明してあるように、このファイルはHeroListComponentというクラス定義とそれに対する@Componentアノテーションで構成されています。

@Componentアノテーションはこれに続くクラスのデコレータで、Angularにたいし直後に続くクラスがコンポネントであることを知らせ、またこのコンポネントをいつインスタンス化するか、どのプロパティとホスト・リスナにバインドするかなどを指定します:

  • selector:このディレクティブのインスタンス化のトリガとなCSSセレクタで、ここでは親のAppComponentの中のテンプレートの中に<hero-list></hero-list>というタグ区間があります

  • directives:このコンポネントに含まれるディレクティブのリストで、ここではHeroDetailComponentコンポネント
  • providers:このディレクティブから可視な注入可能オブジェクトのリストで、ここではHeroServiceのオブジェクト

HeroListComponentクラスは以下のものでできています:

  • heroes プロパティ:ヒーローたちのListで、ライフサイクル・フックngOnInit()メソッドでセットされます

  • selectedHero プロパティ:ユーザ・クリックで選択されたヒーローのオブジェクト

  • HeroListComponentコンストラクタ:Angularがインスタンス化します
  • selectHeroメソッド:selectedHero プロパティをセットする
あるコンポネントがインスタンス化されると、
  • AngularはそのコンポネントのためのシャドウDOMを生成し
  • そのシャドウDOMのなかに選択されたテンプレートをロードし
  • providersで設定された注入可能なすべてのオブジェクトたちを生成します。
総てのテンプレートのなかの式と文がそのコンポネント・インスタンスに対して計算されます。



architectureアプリのテンプレート

テンプレート、メタデータ、及びコンポネントが一緒になってあるビューを記述しています。テンプレートはHTMLファイルとして、あるいは@componentアノテーションのtemplate:プロパティとして作成できます。

Angularはユーザが何を見また何ができるかを、Componentクラスのインスタンス(コンポネント)とユーザが面するテンプレートを介して実現します。コンポネント/テンプレートの二元性はMVC(model-view-controller)あるいはMVVM(model-view-viewmodel)で馴染みのものです。Angularではコンポネントは controller/viewmodelとして、テンプレートがviewとして機能します。

lib/hero_list_component.html

<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>
<hero-detail *ngIf="selectedHero != null" [hero]="selectedHero"></hero-detail>

これはHeroListComponentのテンプレートです。このテンプレートには通常のHTMLとは異なった行が存在します:

  • 順序なしリスト・アイテム・タグ<li>の中には*ngFor="let hero of heroes"が存在している:*ngForAngularの繰り返しディレクティブで、heroesリストのすべてのアイテムheroに対しこのタグに続くテキストを表示します。この変数heroはこのコンテクストのなかで使われています

  • (click)はこのアイテム上でのクリック・イベントがバインドのターゲット(イベント・バインディング)であることを示します。ここではselectedHeroをセットするselectHero(hero)メソッドを呼び出しており、これが<hero-detail>タグ要素で使われています

  • hero.nameを2重波括弧で囲んだ部分は文字列内挿(インターポレーション)でありこの式の値が文字列として表示されます

  • <hero-detail>HTMLのタグではなく、HeroDetailComponentが埋める区間であることを示します

  • *ngIfディレクティブはそれに続く式がteueであるときにこの要素(及び副要素たち)をDOMに付加します

  • [hero]=はプロパティ・バインドで、このコンポネントのプロパティheroselectedHeroの値をセットしてこの区間を表示させます

詳細はテンプレートの構文を参照してください。



architectureアプリのデータ・バインディング

データ・バインディングはテンプレートのパーツたちとコンポネントのパーツたちを連携させるメカニズムです。我々はHTMLのテンプレートにバインドのためのマークアップを付加してAngularに対して双方をどのように結びつけるかを知らせます。

architectureアプリケーションでは3つのコンポネントのテンプレート上で以下のようなバインディングが使われています。

HeloListComponent

方向

コンポネント--DOM

タイプ

記述

内挿

<li>{{hero.name}}</li>

プロパティ

<hero-detail [hero]="selectedHero"></hero-detail>

イベント

<li (click)="selectHero(hero)"></li>

2番目の<hero-detail>タグは、次のクリック・イベントが呼んだselectHero(hero)で得られたselectedHeroオブジェクトをHeroDetailComponentheroプロパティにセットします。


HeloDetailComponent

方向

コンポネント--DOM

タイプ

記述

内挿

<h4>{{hero.name}} Detail</h4>

内挿

<div>Id: {{hero.id}}</div>

双方向

<input [(ngModel)]="hero.name">

双方向

<input [(ngModel)]="hero.power">

下の二つの双方向バインドはngModelディレクティブを使って単一の記述でプロパティとイベントのバインドを組み合わせたものです。入力タグのなかで、表示されるhero.namehero.powerはユーザがこれを変えるたびにこれらのプロパティ値を変更します。双方向バインディング(two-way binding)は入力やフォーム入力タグの中で利用されます。



SalesTaxComponent

方向

コンポネント--DOM

タイプ

記述

内挿

{{ getTax(amountBox.value) | currency:'USD':true:'1.2-2' }}

この内挿の記述はやや複雑です。"|Dartの演算子とは違い、パイプ演算子(pipe operator)といいます。詳細はテンプレート構文の章の式演算子を読んでください。



architectureアプリのサービス

サービスは値、関数、あるいは機能を提供するクラスで、@Injectable()アノテーションが付されています。Angularは必要な時にそのオブジェクトをコンポネントに渡します。その為の依存物注入(dependency injection)はアプリケーション開発には重要なデザイン・パタンです。その詳細は追って解説いたします。

プロバイダ(provider)は依存物の新規インスタンスを生成します。依存物たちを使うコンポネントのクラスは、@componentアノテーションのproviders:プロパティでそれをListの型で宣言します。

architectureアプリケーションでは6つのサービスが存在しますが、それらの受けては次のようになっています:


サービス

受けて

記述

backend_service

helo_service

HeroService(this._logger, this._backendService);

logger_service

helo_service

helo_list_component

providers: const [HeroService]
HeroListComponent(this._heroService);

tax_rate_service

sales_tax_component

providers: const [SalesTaxService, TaxRateService]
SalesTaxComponent(this._salesTaxService) {}

sales_tax_service

hero_serviceはコンポネントではないので、providers:プロパティはありませんが、コンストラクタの中で2つのサービスのオブジェクトをプライベートな変数として保持します。

即ちサービス注入を受ける側はコンストラクタの中でそのサービスを外部からは見えない形でそれらのサービスを保持し、外部からは必要なメソッドでそれらのサービスのデータを使用します。コンストラクタを見ればそのコンポネントやサービスがどのサービスでできているかが一目瞭然です。このパタンはテストが容易という利点もあります。正式のサービスではなくてモックアップのサービスを使うことで、先行してテストを行うことも可能になります。


前のページ

次のページ