AngularDart入門 第2回 HelloWorld改

[第1回](http://qiita.com/laco0416/items/5cad659d6b278c4a2081)では最も単純なAngularDartアプリケーションを作りました。今回はHelloWorld改ということでもう少しAngularDartの機能を使ってみましょう。

今回の元記事は[2. Building Something More than Hello World](https://angulardart.org/tutorial/04-ch02-component.html)です。

# まずはサンプルアプリを動かしてみよう
[こちら](https://github.com/angular/angular.dart.tutorial/tree/master/Chapter_02)にサンプルアプリのソースコードがあります。リポジトリをクローンして、**Chapter_02**ディレクトリで`> pub serve`すればサンプルアプリが起動します。

![Imgur](http://i.imgur.com/u1LEwsx.png)

Recipe Listのアイテムをクリックすると下にレシピの詳細が表示されます。

# スコープを理解する
AngularDartにはスコープ(Scope)という概念があります。スコープはHTMLの要素に対応した入れ子構造になっており、ルートスコープ(Root Scope)が前回説明した`ng-app`が付いた要素です。ルートスコープは必ず`<html>`要素に対して定義されますが、その子になるスコープは自由に定義することができます。ただし、ルートスコープ以外のスコープは必ず親スコープを持ちます。

![Scope diagram](https://angulardart.org/tutorial/img/scope_diagram.png)

# モデルを理解する
前回のコードに次のようなバインディングがありました。

```html
<input type="text" ng-model="name">
```

この`name`はモデルですが、モデルは常にスコープに紐付いています。前回はルートスコープ以外にスコープがありませんでしたので、この`name`はルートスコープ(`ng-app`)に紐付いたモデルだったということです。Angularでモデルを扱う場合は、そのモデルがどのスコープに紐付いているのかを把握していなければなりません。

# コンポーネントを理解する
コンポーネントというのはスコープを持つオブジェクトです。アプリケーションにコンポーネントをひもづけることで、アプリケーション内に独自のスコープを定義することができます。

コンポーネントを定義するには`@Component`アノテーションをクラスに付けます。

```dart:
@Component(
  selector: 'recipe-book',
  templateUrl: 'recipe_book.html'
)
class RecipeBookComponent {
```

`@Component`にはいくつかの引数があります。`selector`はそのコンポーネントのCSSセレクタを設定します。`[recipe-book]`は、html内ではrecipe-book属性として宣言します。宣言には以下の様な種類があります。

- `recipe-book` : DOM要素
- `.recipe-book` : クラス
- `[recipe-book]` : DOM属性
- `[attribute=value]`: 条件付きDOM属性
- `:contains(/recipe-book/) : 任意の文字列を含む要素

`templateUrl`にはそのコンポーネントのテンプレートとなるHTMLを指定します。記述するUrlは基本的には`packages/`から始まる相対パスですが後述するtransformerの設定によりファイル名のみに省略できます。

```html:recipe_book.html
<div><strong>Name: </strong>{{selectedRecipe.name}}</div>

<li ng-click="selectRecipe(recipe)">{{recipe.name}}</li>
```

~~`publishAs`はhtml内でのコンポーネントのインスタンス名です。`publishAs`に設定した名前と同じ名前のgetterを作り、thisを返す必要があります。`publishAs`を設定すると、モデルへのアクセスは`cmp.selectedRecipe`のようになります。設定しなかった場合は`selectedRecipe`とするだけでアクセスできますが、先述の通りスコープは入れ子になっているのでどのスコープに属するモデルなのか明確にするために出来るだけ設定しましょう。~~

>[追記]
publishAsは現在Deprecatedされています。後方互換性のために残されていますが実際の処理は何も行われていません。現在は何もしなくても`@Component`のオブジェクトが暗黙のルートスコープになっています。

このようにして作ったコンポーネントはアプリケーションに紐付けなければなりません。モジュールを宣言し、モジュールにコンポーネントを紐付け、そのモジュールをアプリケーションに追加します。

```dart:main.dart
import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
import 'package:angular_sample/component/recipe_book.dart';

class MyAppModule extends Module {
  MyAppModule() {
    bind(RecipeBookComponent);
  }
}

void main() {
  applicationFactory()
  .addModule(new MyAppModule())
  .run();
}
```

アプリケーションにコンポーネントを紐付けたら、HTML内で呼び出しましょう。

```html
<recipe-book></recipe_book>
```

`<recipe-book>`要素の内側がRecipeBookComponentのスコープになります。

>[追記]
AngularDart 1系ではControllerという概念は取り払っているので、属性によりScopeを付与して操作するという以前の方法はあまり推奨されません（可能ではある）。なので基本的にはすべてComponent化して、テンプレートHTMLを別ファイルに書きます。

AngularDartを使うためには`angular`パッケージへの依存を記述します。web_componentsはWeb Componentsに対応していない古いブラウザに対するポリフィルのパッケージです。外してもいいですが、その場合は後述のHTML内のweb_components依存部分を削除してください。

## [追記]transformerによるHTMLファイルの変換
Componentの`templateUrl`に指定するHTMLファイルや、`cssUrl`に指定するCSSファイルは基本的には`packages/angular_sample/component/recipe_book.html`のような相対パスを書く必要がありますが、これはとても面倒なのでAngularDartではpubspecのtransformerを使って`recipe_book.html`だけで記述できるようにしてあります。

pubspec.yamlのtransformersに次のように追記します。

```yaml
transformers:
- angular:
    html_files:
      - lib/component/recipe_book.html
```

# Angularの機能

## ng-repeat
`ng-repeat`は、反復可能なオブジェクトに対して、テンプレートに当てはめて子要素を追加します。今回の内容がなんとなく理解できていれば、`ng-repeat="recipe in cmp.recipes"`が`publishAs:recipe`なコンポーネントの定義であることがわかると思います。

```html
<ul>
  <li class="pointer" ng-repeat="recipe in recipes" ng-click="selectRecipe(recipe)">
    {{recipe.name}}
  </li>
</ul>
```

## ng-click
`ng-click`はクリックイベントが起きた時に実行する式を設定できます。

# まとめ

- モデルはスコープに属する
- コンポーネントはスコープを持つ
- コンポーネントはアプリケーションに紐付ける

[第3回](http://qiita.com/laco0416/items/1e969a20c92b9e3c0a3c)は[3. Creating a Custom Component
](https://angulardart.org/tutorial/05-ch03-component.html)を元にバージョン1.0に置き換えて解説します。