この記事はAngular+Firebaseでチャットアプリを作るのエントリーです。
前記事:Angularでビュー(チャット画面)を作る
次記事:AngularのngIfとsubmitイベントでチャットコメントを実装する
この記事で行うこと
本稿では、AngularのビルトインディレクティブであるngModel、ngForを使ってDOMを動的に操作していきます。
Angularのビルトインディレクティブ
Angularでは、AngularJS1.xxで使われていたビルトインディレクティブが、記法を変えて使用できるようになっています。
このビルトインディレクティブ、要はhtmlの中にJavaScriptのforやらifやらといった文法を取り入れてしまおうというもの。直感的にDOMの操作ができるようになるので便利です。
使い方はReactやVue.jsといった他のJavaScriptフレームワークと共通する部分も多いので、一度ビルトインディレクティブの使用方法を覚えれば、他のフレームワークでもDOM要素の中に条件文をいれる勘所がわかってくると思います。
(追記:2020/6)現時点(2020年6月)での最新の内容に書き換えています。
実装内容
ngModelによる双方向バインディング
AngularJS1.xxでもてはやされた双方向バインディングですが、Angularでも使うことができます。
実験的に、チャットのコメントフォームと出力されたコメントを連動させてみます。
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 { }
export class AppComponent {
public content = '';
}
<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)]
という書き方がきもいと某イベントで話題になっていました。公式のこのあたりの説明を見ると、単方向と双方向をわけるためにこの記述になったみたいです。
ngForでコメントを繰り返し表示させる
commentsオブジェクトを作る
さて、ngFor
を使うにあたりまずはcomments
オブジェクトをapp.component.ts
に記述してみます。この時注意する点は、TypeScriptなので型の定義を忘れないことです。
export class AppComponent {
content = '';
comments: { name: string, content: string }[] = [
{ name: 'Suzuki Taro', content: '1つ目のコメントです。'},
{ name: 'Suzuki Taro', content: '2つ目のコメントです。'},
{ name: 'Suzuki Taro', content: '3つ目のコメントです。'}
];
}
型の定義はクラスファイルにまとめていきます。
interfaceでも型の定義は可能ですが、公式のスタイルガイドによるとinterfaceはなるべく使わず、クラスを使って定義していくことが推奨されています。
ng g class class/chat
作成されたchat.tsファイルにCommentクラスを作成します。
export class Comment {
name: string;
content: string;
}
commentsオブジェクトを定数としてAppComponentの外に出し、importしたCommentを型として使用します。
import { Component } from '@angular/core';
import { Comment } from './class/chat'; // 追加
const COMMENTS: Comment[] = [ // 追加
{ name: "Suzuki Taro", content: "1つ目のコメントです。"},
{ name: "Suzuki Taro", content: "2つ目のコメントです。"},
{ name: "Suzuki Taro", content: "3つ目のコメントです。"}
];
@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
は多用されるディレクティブの1つです。
ngFor
はul
、li
タグで使われることが多いですが、階層構造がしっかりしていればdiv要素でも使用可能です。ngFor
が設置された階層より下にある階層のDOMを、指定された配列がなくなるまで繰り返します。
<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
等)が表示されることを防ぐことができます。よりセマンティックなコードを実現したい場合に、このディレクティブを使用します。
実行結果
登録したコメントが表示されました。
ただこれだとSuzukiさんばかりコメントしていてTanakaさんが可哀想なので、次はTanakaさんにもコメントしてもらいます。
ソースコード
この時点でのソースコード