AngularのngForでチャットコメントを実装する

> この記事は[Angular+Firebaseでチャットアプリを作る](http://qiita.com/Yamamoto0525/items/a76ea4b3924eeb82b0f9)のエントリーです。
> 前記事：[Angularでビュー（チャット画面）を作る](http://qiita.com/Yamamoto0525/items/94613c79ef2aa461b91e)
> 次記事：[AngularのngIfとsubmitイベントでチャットコメントを実装する](http://qiita.com/Yamamoto0525/items/a6995da3ffbb4985c5e2)

# この記事で行うこと

本稿では、AngularのビルトインディレクティブであるngModel、ngForを使ってDOMを動的に操作していきます。

- [ngModelによる双方向データバインディング](http://qiita.com/Yamamoto0525/items/5ee362967295ec4d2c65#ngmodel%E3%81%AB%E3%82%88%E3%82%8B%E5%8F%8C%E6%96%B9%E5%90%91%E3%83%90%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)
- [ngForでコメントを繰り返し表示させる](http://qiita.com/Yamamoto0525/items/5ee362967295ec4d2c65#ngfor%E3%81%A7%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97%E8%A1%A8%E7%A4%BA%E3%81%95%E3%81%9B%E3%82%8B)

### Angularのビルトインディレクティブ

Angularでは、AngularJS1.xxで使われていたビルトインディレクティブが、記法を変えて使用できるようになっています。

このビルトインディレクティブ、要はhtmlの中にJavaScriptのforやらifやらといった文法を取り入れてしまおうというもの。直感的にDOMの操作ができるようになるので便利です。
使い方はReactやVue.jsといった他のJavaScriptフレームワークと共通する部分も多いので、一度ビルトインディレクティブの使用方法を覚えれば、他のフレームワークでもDOM要素の中に条件文をいれる勘所がわかってくると思います。

*****
（追記：2020/6）現時点（2020年6月）での最新の内容に書き換えています。
*****

# 実装内容

## ngModelによる双方向バインディング

AngularJS1.xxでもてはやされた双方向バインディングですが、Angularでも使うことができます。
実験的に、チャットのコメントフォームと出力されたコメントを連動させてみます。

```ts:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // 追加

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgbModule,
    FormsModule // 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

```ts:src/app/app.component.ts
export class AppComponent {
  public content = '';
}
```

```html:src/app/app.component.html
<div class="page">
  <section class="card">
    <div class="card-header">
      NgChat
    </div>
    <div class="card-body">
      <div class="media">
        <div class="media-left">
          <a href="#" class="icon-rounded">S</a>
        </div>
        <div class="media-body">
          <h4 class="media-heading">Suzuki Taro Date:2016/09/01</h4>
          <div>この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。</div>
        </div>
      </div>
      <hr>
      <div class="media">
        <div class="media-body">
          <h4 class="media-heading">Tanaka Jiro Date:2016/09/01</h4>
          <div>{{content}}</div><!--contentに変更-->
        </div>
        <div class="media-right">
          <a href="#" class="icon-rounded">T</a>
        </div>
      </div>
    </div>
  </section>

  <section>
    <form class="chart-form">
      <div class="input-group">
        <input type="text" class="form-control"
               [(ngModel)]="content"
               name="comment"
               placeholder="Comment" /><!--ngModel追加-->
        <div class="input-group-append">
          <button class="btn btn-info" type="button">SEND</button>
        </div>
      </div>
    </form>
  </section>
</div>

```
> #### FormModuleの追加
> `[(ngModel)]`はFormModuleに搭載されているディレィクティブで、使用する場合は`@NgModule`でFormModuleをimportしておく必要があります。このように、Angularは最小単位の構成に対し、必要に応じて機能を追加していくスタイルをとっています。

### 実行結果

フォームの入力に合わせて、`content`で紐付けたDOMがリアルタイムで変更されていきます。これがAngularの双方向バインディングです。
`[(ngModel)]`という書き方がきもいと[某イベントで話題](https://codeiq.jp/magazine/2015/05/24294/)になっていました。公式の[このあたりの説明](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#binding-syntax)を見ると、単方向と双方向をわけるためにこの記述になったみたいです。

![ezgif-1-8e71aa0ba3.gif](https://qiita-image-store.s3.amazonaws.com/0/112566/d988bf29-b9d7-fb81-6fa8-1393002013d5.gif)

## ngForでコメントを繰り返し表示させる

### commentsオブジェクトを作る

さて、`ngFor`を使うにあたりまずは`comments`オブジェクトを`app.component.ts`に記述してみます。この時注意する点は、TypeScriptなので型の定義を忘れないことです。

```ts:src/app/app.component.ts
export class AppComponent {
  content = '';
  comments: { name: string, content: string }[] = [
    { name: 'Suzuki Taro',  content: '１つ目のコメントです。'},
    { name: 'Suzuki Taro',  content: '２つ目のコメントです。'},
    { name: 'Suzuki Taro',  content: '３つ目のコメントです。'}
  ];
}
```

型の定義はクラスファイルにまとめていきます。
interfaceでも型の定義は可能ですが、[公式のスタイルガイド](https://angular.io/docs/ts/latest/guide/style-guide.html)によるとinterfaceはなるべく使わず、クラスを使って定義していくことが推奨されています。

```bash
ng g class class/chat
```

作成されたchat.tsファイルにCommentクラスを作成します。

```ts:src/app/class/chat.ts
export class Comment {
  name: string;
  content: string;
}
```
commentsオブジェクトを定数としてAppComponentの外に出し、importしたCommentを型として使用します。

```ts:src/app/app.component.ts
import { Component } from '@angular/core';
import { Comment } from './class/chat'; // 追加

const COMMENTS: Comment[] = [ // 追加
  { name: "Suzuki Taro",  content: "１つ目のコメントです。"},
  { name: "Suzuki Taro",  content: "２つ目のコメントです。"},
  { name: "Suzuki Taro",  content: "３つ目のコメントです。"}
];

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  public content = '';
  public comments = COMMENTS; // 追加
}
```

これでコンポーネント側の準備は整いました。

### `ngFor`をHTMLテンプレートに記述する

ここでようやく`ngFor`の登場です。
AngularJS1.xxを触ったことのある方なら、`ng-repeat`のAngularバージョンといった方がわかりやすいかもしれません。`ng-repeat`同様、Angularでも`ngFor`は多用されるディレクティブの１つです。

`ngFor`は`ul`、`li`タグで使われることが多いですが、階層構造がしっかりしていればdiv要素でも使用可能です。`ngFor`が設置された階層より下にある階層のDOMを、指定された配列がなくなるまで繰り返します。

```html:src/app/app.component.html
<div class="page">
  <section class="card">
    <div class="card-header">
      NgChat
    </div>
    <div class="card-body">
      <ng-container *ngFor="let comment of comments"><!--追加-->
      <div class="media">
        <div class="media-left">
          <a href="#" class="icon-rounded">S</a>
        </div>
        <div class="media-body">
          <h4 class="media-heading">{{comment.name}} Date:2016/09/01</h4><!--comment.nameに変更-->
          <div>{{comment.content}}</div><!--comment.contentに変更-->
        </div>
      </div>
      <hr>
      </ng-container>
      <div class="media">
        <div class="media-body">
          <h4 class="media-heading">Tanaka Jiro Date:2016/09/01</h4>
          <div>{{content}}</div><!--contentに変更-->
        </div>
        <div class="media-right">
          <a href="#" class="icon-rounded">T</a>
        </div>
      </div>
    </div>
  </section>

  <section>
    <form class="chart-form">
      <div class="input-group">
        <input type="text" class="form-control"
               [(ngModel)]="content"
               name="comment"
               placeholder="Comment" /><!--ngModel追加-->
        <div class="input-group-append">
          <button class="btn btn-info" type="button">SEND</button>
        </div>
      </div>
    </form>
  </section>
</div>
```
>#### `ng-container`ディレクティブ
>今回の`ngFor`には`ng−container`ディレクティブを使用しました。
>このディレクティブは実行後のソースコードに表示されないため、意味のないタグ（`div`等）が表示されることを防ぐことができます。よりセマンティックなコードを実現したい場合に、このディレクティブを使用します。

### 実行結果

![NgChat (1).png](https://qiita-image-store.s3.amazonaws.com/0/112566/04862859-5691-01e2-4ac0-5813b407847d.png)

登録したコメントが表示されました。
ただこれだとSuzukiさんばかりコメントしていてTanakaさんが可哀想なので、次はTanakaさんにもコメントしてもらいます。

## ソースコード

この時点での[ソースコード](https://github.com/Yamamoto0525/NgChat/commit/2f14dcbc9a42af28cf9019be808d29bc8d6e7003)