こんにちは。サイオステクノロジー技術部 武井です。
今回は、Java Scriptフレームワーク「Angular」の紹介と、Angularで簡単な掲示板システムを作成したいと思います。
Angularはクライアントサイドフレームワークです。その名の通り、クライアントを作成することに特化したものです。従来のWebシステムでは、サーバー側でHTMLを生成し、そのHTMLをブラウザが受け取って、画面を表示していました。
しかしながら、スマートフォンなどのマルチデバイス対応が求められる昨今、サーバーはRestfulなインターフェースでJOSNのみを返し、そのJSONによって、クライアント側で画面を生成するのが主流です。Agularもそんなフレームワークです。基本的には、ビューはクライアント側で持ち、サーバーから受け取ったJSONに基づき、クライアント側でHTMLを生成します。
Angularの記述言語はType Scriptです。Type Scriptで記述して、node.jsによってJava Scriptにコンパイル・ビルドします。なので、開発にはnode.jsは必須となります。
まずは、開発環境の構築から、ご説明致します。
開発環境の構築
まず、node.jsのバージョンを管理するためのツールnodebrewをインストールします。
$ curl -L git.io/nodebrew | perl - setup $ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
次に、node.jsをインストールします。
$ nodebrew install-binary stable
こうすると、最新の安定バージョンがインストールできます。インストールされたバージョンを確認してみます。
$ nodebrew ls v9.2.1
今は9.2.1がstableのようです。では、このバージョンのnode.jsを使いたいので、以下のように入力します。
$ nodebrew use v9.2.1
これでバージョン9.2.1が使えるようになりました。
以上で、開発環境の構築は完了です。
掲示板システムの構築
では、Angularで本格的なアプリを作ってみたいと思います。CRUD(Create、Read、Update、Delete)を試すことができる簡単な掲示板です。私は、新しいフレームワークを試すときは、必ずこのアプリを作ります。本アプリケーションのソースは以下のGitHubで公開しています。
https://github.com/noriyukitakei/BBSRestAPI
アプリケーションの画面は以下のとおりです。
まずログイン画面です。
ログインすると、メッセージの一覧画面が表示されます。新規登録ボタンをクリックすると、新規にメッセージを登録する画面に遷移します。タイトルをクリックするとメッセージ変更画面に遷移し、削除をクリックするとメッセージが削除されます。
メッセージの新規登録画面です。
メッセージの変更画面です。
Angularのソースコードを説明する前に、本アプリケーションが利用するRest APIのインターフェースの説明をします。先程もご説明したように、AngularはサーバーサイドからJSONを受け取り、そのJSONによってクライアントサイドにて画面を生成します。なので、JSONを返却するRest APIが必須になります。本アプリケーションでは、以下の仕様のRest APIがあることを前提としています。ちなみにこのRest APIのソースは以下のgitHubにて公開しております。
https://github.com/noriyukitakei/BBSRestAPI
■ 認証API
認証のAPIです。ログインIDとパスワードを送ると、アクセストークンとその有効期限が返ります。以降のAPIでは、HTTPリクエストのAuthorizationヘッダにアクセストークンを付与して下さい。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/login
–<Method>–
Post
–<リクエストボディ>–
下記の形式のJSONになります。
{ id:[ログインID] password:[パスワード] }
–<レスポンスボディ>–
下記の形式のJSONになります。
{ access_token:[アクセストークン] expires_in:[アクセストークンの有効期限(秒)] }
■ メッセージ一覧API
メッセージの一覧を取得するAPIです。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/messages
–<Method>–
Get
–<リクエストヘッダ>–
Authorization: Bearer [認証APIで取得したアクセストークン]
–<リクエストボディ>–
なし
–<レスポンスボディ>–
下記の形式のJSONになります。
[ { id:[メッセージID] title:[タイトル] author:[投稿者] comment:[コメント] }, ... ]
■ メッセージ取得API
メッセージの一覧を取得するAPIです。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/masseges/[メッセージID]
–<Method>–
Get
–<リクエストヘッダ>–
Authorization: Bearer [認証APIで取得したアクセストークン]
–<リクエストボディ>–
なし
–<レスポンスボディ>–
下記の形式のJSONになります。
{ title:[タイトル] author:[投稿者] comment:[コメント] }
■ メッセージ投稿API
メッセージを投稿するAPIです。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/messages/new
–<Method>–
Post
–<リクエストヘッダ>–
Authorization: Bearer [認証APIで取得したアクセストークン]
–<リクエストボディ>–
下記の形式のJSONになります。
{ title:[タイトル] author:[投稿者] comment:[コメント] }
–<レスポンスボディ>–
なし
■ メッセージ変更API
メッセージを変更するAPIです。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/messages/update
–<Method>–
Post
–<リクエストヘッダ>–
Authorization: Bearer [認証APIで取得したアクセストークン]
–<リクエストボディ>–
下記の形式のJSONになります。
{ id:[メッセージID] title:[タイトル] author:[投稿者] comment:[コメント] }
–<レスポンスボディ>–
なし
■ メッセージ削除API
メッセージを削除するAPIです。
–<リクエストURL>–
https://[Rest APIサーバーのホスト名]/messages/[メッセージID]
–<Method>–
Delete
–<リクエストヘッダ>–
Authorization: Bearer [認証APIで取得したアクセストークン]
–<リクエストボディ>–
なし
–<レスポンスボディ>–
なし
アプリケーションのディレクトリ構成は以下のようになっております。
では、それぞれのソースについて説明いたします。
■ app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/https' // (1) import { MY_ROUTES } from './app.routing'; // (2) import { AppComponent } from './app.component'; import { LoginComponent } from './component/login/login.component'; // (3) import { ListComponent } from './component/list/list.component'; import { AddComponent } from './component/add/add.component'; import { UpdateComponent } from './component/update/update.component'; import { MessageService } from './service/message.service'; // (4) import { AuthService } from './service/auth.service'; @NgModule({ declarations: [ // (5) LoginComponent, AppComponent, ListComponent, AddComponent, UpdateComponent, ], imports: [ BrowserModule, // (6) FormsModule, JsonpModule, HttpModule, MY_ROUTES ], providers: [ MessageService, AuthService ], // (7) bootstrap: [AppComponent] // (8) }) export class AppModule { }
一番最初の呼ばれるルートモジュールと呼ばれるものです。Angularの全てはここから始まります。このモジュールでは、他のところで使うモジュール、コンポーネント、サービス、ルーター等を定義します。主要なもののみピックアップしてご説明します。
(1)では、HTTPクライアントを利用するためのモジュールをインポートしています。Rest APIサーバーからJSONを取得するのに使います。
(2)では、ルーティングの定義ファイルをインポートしています。ルーティングとは、URLのパスと、それに対応して呼ばれるコンポーネントの組み合わせです。
(3)では、各種コンポーネントをインポートしています。メッセージの追加、削除、更新、一覧取得、認証などの画面を表示するためのコンポーネントになります。
(4)では、各種サービスを定義しています。Angularでもアプリケーションに必要な処理はサービスに分離する手法を取ります。
(5)では、アプリケーションの中で使うコンポーネントを宣言しています。ここで宣言しないと、コンポーネントは使えません。
(6)では、アプリケーションの中で使うモジュールをインポートしています。ここで宣言しないと、モジュールは使えません。
(7)では、アプリケーションの中で使うサービスを定義しています。ここで宣言しないと、サービスは使えません。
(8)では、一番最初に起動するコンポーネントを指定しています。
■ app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent { }
ルートコンポーネントです。一番最初に呼ばれます。特にこのコンポーネントの中では何もしていません。ただ、templateUrlで定義しているapp.component.htmlを呼び出しているだけです。
■ app.component.html
<div style="text-align: center;"> <h1>掲示板</h1> </div>
ルートコンポーネントから呼ばれるHTMLになります。特筆すべきは(1)ですが、これは、後に説明するHTML内のrouterLinkの属性で定義したパスが表示されるエリアになります。詳細は後述します。
■ app.routing.ts
import { ModuleWithProviders } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LoginComponent } from './component/login/login.component'; // (1) import { ListComponent } from './component/list/list.component'; import { AddComponent } from './component/add/add.component'; import { UpdateComponent } from './component/update/update.component'; import { ErrorComponent } from './component/error/error.component'; const myRoutes = [ // (2) { path: '', component: ListComponent }, // (3) { path: 'messages/:id', component: UpdateComponent }, // (4) { path: 'messages/delete/:id', component: ListComponent }, { path: 'add', component: AddComponent }, { path: 'login', component: LoginComponent }, { path: 'error', component: ErrorComponent } ]; export const MY_ROUTES: ModuleWithProviders = RouterModule.forRoot(myRoutes);
(1)で必要なコンポーネントをインポートしています。
(2)でURLのパスと、それに対応して呼ばれるコンポーネントを定義しています。
例えば(3)では、https://[Webサーバーのホスト名]/にアクセスした際に呼ばれるコンポーネントを定義しています。
(4)では、https://[Webサーバーのホスト名]/messages/1にアクセスした際に呼ばれるコンポーネントを定義しています。:idの部分に入力した文字列は、コンポーネントに引き渡されます。後ほど後述します。
■ auth.service.ts
import { Injectable } from '@angular/core'; // (1) import { Http,Headers } from '@angular/https'; // (2) import { AuthInfo } from '../dto/auth.info'; // (3) import { Observable } from 'rxjs/Observable'; // (4) import 'rxjs/add/operator/map'; // (5) @Injectable() // (6) export class AuthService { constructor(private https: Http) { } // (7) login(id: string,password: string): Observable { // (8) const header = new Headers({'Content-Type': 'application/json'}); // (9) let user = { // (10) id: id, password: password } return this.https.post('https://localhost:8080/auth',user,{headers:header}) // (11) .map( response => { let authInfo: AuthInfo = new AuthInfo; let json = response.json(); // (12) authInfo.access_token = json['access_token']; authInfo.expires_in = json['expires_in']; return authInfo; } ); } }
認証処理を行うサービスです。Angularでもソースコードの保守性を高めるために、よく使われる処理をサービスに分離します。
(1)は、サービスの依存性注入に必要なアノテーションをインポートしています。
(2)は、HTTP通信に必要なモジュールです。
(3)は、認証情報を格納するDTOをインポートしています。
(4)は、非同期通信に必要なObservableモジュールをインポートしています。
(5)も(4)と同様です。
(6)は、Injectableアノテーションです。このアノテーションをつけたサービスはAngularが管理するDIコンテナの中に入り、後から依存性注入できるようになります。
(7)は、(2)でインポートしたHTTP通信に必要なサービスを依存性注入しています。
(8)はメソッドの定義です。ユーザーIDとパスワードが引数になっています。戻り値はObservableです。HTTPによる非同期通信を行う際にはObservableは必須になります。
(9)は、リクエストに付与するHTTPヘッダを定義しています。
(10)は、認証APIのリクエストボディにセットするJSONを定義しています。
(11)は、HTTPモジュールによってHTTP通信をしています。postメソッドの第1引数にRest APIのURL、第2引数にリクエストボディ、第3引数にリクエストヘッダをセットします。その後にメソッドチェーンでmapメソッドをつけます。mapメソッドの引数はファンクションになります。responseにはHTTPレスポンスがセットされ、そのHTTPレスポンスをどのように処理するかをアロー関数で定義しています。
(12)は、jsonメソッドでJSON本体を取得しています。認証情報を格納するDTOにセットして、戻り値として返却しています。
■ message.service.ts
import { Injectable } from '@angular/core'; import { Message } from '../dto/message'; import { Http,Headers } from '@angular/https'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; @Injectable() export class MessageService { constructor(private https: Http) { } getMessage(id: string):Observable { let accessToken = localStorage.getItem('access_token'); // (1) const header = new Headers({'Authorization': 'Bearer ' + accessToken}); // (2) return this.https.get('https://localhost:8080/messages/' + id,{headers:header}).map ( // (3) response => { let message: Message = new Message; let json = response.json(); message.id = json['id']; message.author = json['author']; message.title = json['title']; message.comment = json['comment']; return message; } ).catch(e => { // (4) if (e.status === 401) { return Observable.throw('Unauthorized'); // (5) } }) } ・・・
投稿されたメッセージを操作するサービスになります。ここでご説明するのは、特定のメッセージIDのメッセージを取得するメソッドです。
(1)は、ローカルストレージ(HTML5の機能でブラウザに情報を格納するストレージのようなもので、平たく言うとCookieの進化版)に格納された認証情報を取り出しています。認証処理のところで、認証APIのレスポンスであるアクセストークンをローカルストレージに格納します(後述)。ここでは、そのアクセストークンを取り出しています。
(2) (1)で取得したアクセストークンをHTTPヘッダに付与するために、HTTPモジュールのHeadersオブジェクトを生成してます。つまり、APIを呼び出すときにAuthorizationのHTTPヘッダーにアクセストークンを付与しています。Rest API側では、このアクセストークンを検証して、問題なければレスポンスを返し、そうでなければHTTPステータスコード401を返します。
(3)は、APIを呼び出しています。Getメソッドため、getを呼び出しており、第1引数にAPIのURL、第2引数に(2)で生成したヘッダー情報をセットします。後は先程説明した認証APIのところと処理は同じです。
(4)はエラー処理です。おそらくHTTPステータスコード200以外が返されたときに、catch内に遷移するものと思われます。ここでは、Rest APIが401を返したときに、呼び出し元に例外をthrowしています。
addMessage(message: Message): Observable{ let accessToken = localStorage.getItem('access_token'); const headers = new Headers({'Authorization': 'Bearer ' + accessToken}); headers.append('Content-Type', 'application/json'); let json = { title: message.title, author: message.author, comment: message.comment } return this.https.post('https://localhost:8080/messages/new',json,{ // (1) headers: headers }).map ( response => {} ).catch(e => { if (e.status === 401) { return Observable.throw('Unauthorized'); } }) }
message.service.tsの続きです。メッセージを投稿するサービスになります。特筆すべきところはありません。
(1)で、postメソッドで、メッセージを投稿するAPIを呼び出しています。後は、他と同じです。
deleteMessage(id: String): Observable { let accessToken = localStorage.getItem('access_token'); const headers = new Headers({'Authorization': 'Bearer ' + accessToken}); headers.append('Content-Type', 'application/json'); return this.https.delete('https://localhost:8080/messages/' + id,{ // (1) headers: headers }).map ( response => {} ).catch(e => { if (e.status === 401) { return Observable.throw('Unauthorized'); } }) }
message.service.tsの続きです。メッセージを削除するサービスになります。
他と違うのは、メッセージを削除するAPIはdeleteメソッドなので、(1)でdeleteを呼び出しています。後の処理は他と同じですね。
他にもメッセージを更新するサービスと、メッセージの一覧を取得するサービスがありますが、ほとんど構成は同じなので、説明は割愛します。
■ login.component.ts
import { Component, Injector } from '@angular/core'; import { AuthService } from '../../service/auth.service'; import { AuthInfo } from '../../dto/auth.info'; import { Router } from '@angular/router'; // (1) @Component({ selector: "my-app", templateUrl: './login.component.html', }) export class LoginComponent { constructor(private injector: Injector,private router:Router) {} // (2) user ={ id: '', password: '' } login() { let authService = this.injector.get(AuthService); // (3) authService.login(this.user.id,this.user.password).subscribe( // (4) response => { localStorage.setItem("access_token",response.access_token); // (5) this.router.navigate(["/"]); // (6) }, error => {} ) } }
ログイン画面を表示するためのコンポーネントになります。
(1)は、Routerモジュールをインポートしています。これは、ログインした後に、メッセージ一覧表示画面に遷移したいので、Routerモジュールが必要になるためです。
(2)は、依存性注入を行うためのInjector、ルーティングを行うためのRouterを定義しています。
(3)は、先程説明した認証を行うサービスを依存性注入しています。
(4)は認証を行うサービスのloginメソッドを呼び出しています。引数にはユーザーIDとパスワードを指定しています。メソッドチェーンでsubscribeメソッドをつなげて、第1引数に成功時の処理、第2引数にエラー時の処理を定義します。
(5)は成功時の処理です。responseを引数にして、アロー関数で処理内容を定義しています。responseにはHTTPレスポンスがセットされます。ここでは、レスポンスは認証サービス(auth.service.tsのloginメソッドの戻り値である認証情報)が返却するアクエストークンを含むJSONなので、それを解析して、ローカルストレージに格納しています。メッセージを表示したり追加したりするサービスでAuthorizationヘッダにセットしていたアクセストークンは、ここでローカルストレージに格納していたのです。
(6)ではメッセージ一覧画面に遷移しています。Routerモジュールで実現しています。
■ login.component.html
<form novalidate=""> <div><label for="id">ID:</label> <input id="id" name="id" required="" type="text" /> IDは必須です。</div> <div><label for="password">パスワード:</label> <input id="password" name="password" required="" type="text" /> パスワードは必須です。</div> <div><input disabled="disabled" type="submit" value="送信" /> <input disabled="disabled" type="reset" value="リセット" /></div> </form>
ログイン画面のHTMLになります。先程のlogin.component.tsから呼び出されます。
■ list.component.ts
import { Component, Injector } from '@angular/core'; import { Message } from '../../dto/message'; import { MessageService } from '../../service/message.service'; import { Router } from '@angular/router'; import 'rxjs/add/observable/throw'; // (1) @Component({ selector: 'app-root', templateUrl: './list.component.html' }) export class ListComponent { messages :Message[]; constructor(private injector: Injector,private router:Router) {} ngOnInit() { // (2) let messageService = this.injector.get(MessageService); messageService.getMessages().subscribe( // (3) response => { this.messages = response; }, error => { // (4) if (error === 'Unauthorized') { this.router.navigate(["/login"]); } } ); }
メッセージ一覧画面を表示するコンポーネントです。
(1)は、後述する(4)でエラー処理を行うために必要なインポート処理です。
(2)は、コンポーネント起動時に一番最初に呼ばれる処理を定義しています。ここでメッセージの一覧を定義しています。
(3)は、メッセージを取得するサービスから、メッセージの一覧を取得するメソッドを呼び出しています。成功時はインスタンス変数で定義したmessagesにレスポンスを入れています。
(4)は、サービス側で発生した例外をキャッチする処理です。先述しましたようにサービス側では、Rest APIのHTTPステータスコードが401が返却されたときに例外をthrowしています。それをキャッチする処理です。Routerモジュールによりログイン画面に遷移しています。
delete(id: string) { let messageService = this.injector.get(MessageService); messageService.deleteMessage(id).subscribe( // (1) response => { location.reload(); // (2) }, error => { if (error === 'Unauthorized') { this.router.navigate(["/login"]); } } ); }
list.component.tsの続きです。メッセージを削除するメソッドです。
(1)は、メッセージを処理サービスの、メッセージを削除するメソッドを呼び出しています。
(2)は、成功時の処理です。もう一度一覧画面を表示するためにページ全体をリロードしています。削除したメッセージを一覧画面に反映するためです。
■ list.component.html
<a routerLink="/add">新規登録</a> // (1) <table border="1"> <tr> <td>No</td> <td>タイトル</td> <td>投稿者</td> <td>コメント</td> <td>削除</td> </tr> <tr *ngFor="let message of messages"> // (2) <td>{{message.id}}</td> <td><a href="/messages/{{message.id}}">{{message.title}}</a></td> // (3) <td>{{message.author}}</td> <td>{{message.comment}}</td> <td><a href="#" (click)="delete(message.id)">削除</a></td> // (4) </tr> </table> <router-outlet></router-outlet>
メッセージを一覧画面を表示するHTMLです。
(1)は、メッセージの新規登録画面に遷移するリンクです。routerLinkに、Routerコンポーネントで定義したパスを入れることにより、それに対応したコンポーネントが呼ばれる仕組みです。
(2)は、コンポーネントからデータバインディングされたメッセージの一覧情報をngForでループ処理しています。
(3)は、メッセージ変更画面へのリンクです。
(4)は、メッセージを削除するためのリンクです。(link)のイベントプロパティにより、list.component.tsで定義したdeleteメソッドが呼ばれます。引数には、メッセージIDを渡しています。このリンクをクリックすると、メッセージは削除されます。
■ add.component.ts
import { Component, Injector } from '@angular/core'; import { MessageService } from '../../service/message.service'; import { Message } from '../../dto/message'; import { Router } from '@angular/router'; @Component({ selector: "my-app", templateUrl: './add.component.html', }) export class AddComponent { constructor(private injector: Injector,private router:Router) {} message = { title: '', author: '', comment: '', }; add() { let message = new Message; message.author = this.message.author; // (1) message.title = this.message.title; message.comment = this.message.comment; let messageService = this.injector.get(MessageService); messageService.addMessage(message).subscribe( // (2) response => { this.router.navigate(["/"]); // (3) }, error => {} ); } }
メッセージを新規登録するためのコンポーネントです。
(1)には、新規登録画面で入力フォームで入力された値が、双方向データバインディングによってセットされます。
(2)は、メッセージを操作するサービスの、メッセージ追加するメソッドをを呼び出しています。
(3) 処理成功時には、メッセージ一覧画面に遷移します。
■ add.component.html
<form novalidate=""> <div><label for="title">タイトル:</label> <input id="title" title="" name="title" required="" type="text" /> タイトルは必須です。</div> <div><label for="author">投稿者:</label> <input id="author" name="author" required="" type="text" /> 投稿者は必須です。</div> <div><label for="comment">コメント:</label> <input id="comment" name="comment" required="" type="text" /> コメントは必須です。</div> <div><input disabled="disabled" type="submit" value="送信" /> <input disabled="disabled" type="reset" value="リセット" /></div> </form>
メッセージを登録画面のHTMLです。Angularの構文に従った一般的なHTMLなので、特筆すべきところはありません。
■ update.component.ts
import { Component, Injector } from '@angular/core'; import { MessageService } from '../../service/message.service'; import { Message } from '../../dto/message'; import { ActivatedRoute } from '@angular/router'; import { Router } from '@angular/router'; @Component({ selector: "my-app", templateUrl: './update.component.html', }) export class UpdateComponent { constructor(private injector: Injector,private activatedRoute:ActivatedRoute,private router:Router) {} id = this.activatedRoute.snapshot.paramMap.get('id'); // (1) message = { id: this.id, title: '', author: '', comment: '', }; ngOnInit() { // (2) let messageService = this.injector.get(MessageService); messageService.getMessage(this.id).subscribe( // (3) response => { this.message = response; // (4) }, error => {} ); } update() { let messageService = this.injector.get(MessageService); messageService.updateMessage(this.message).subscribe( // (5) response => { this.router.navigate(["/"]); // (6) }, error => {} ) } }
メッセージを更新するコンポーネントになります。
(1)はメッセージ更新画面を表示する際にURLで指定されたメッセージID(https://[Webサーバーのホスト名]/messages/1)の1の部分を取得するための処理になります。
(2)は、コンポーネントが呼び出されるときに一番最初に行われる処理で、更新画面のフォームに設定する初期値を取得する処理をしています。
(3)は、指定したメッセージIDのメッセージ情報を取得するメソッドを呼び出しています。
(4)は、成功時の処理で、メッセージのインスタンス変数にレスポンス情報を格納しています。
(5)は、メッセージ更新画面で、更新ボタンが押下されたときの処理になります。メッセージを処理するサービスの、メッセージを更新するメソッドを呼び出しております。
(6)は、成功時にメッセージ一覧画面に表示する処理です。
■ update.component.html
<a routerLink="/">一覧に戻る</a> <form #myForm="ngForm" (ngSubmit)="update()" novalidate> <div> <label for="title">タイトル:</label> <input id="title" name="title" [(ngModel)]="message.title" #title="ngModel" required /> <span *ngIf="title.errors?.required && title.dirty">タイトルは必須です。</span> </div> <div> <label for="author">投稿者:</label> <input id="author" name="author" [(ngModel)]="message.author" #author="ngModel" required /> <span *ngIf="author.errors?.required && author.dirty">投稿者は必須です。</span> </div> <div> <label for="comment">コメント:</label> <input id="comment" name="comment" [(ngModel)]="message.comment" #comment="ngModel" required /> <span *ngIf="comment.errors?.required && comment.dirty">コメントは必須です。</span> </div> <div> <input type="submit" value="送信" [disabled]="myForm.invalid" /> <input type="reset" value="リセット" [disabled]="myForm.pristine" /> </div> </form>
メッセージを更新する画面のHTMLです。新規登録画面とほぼ変わらず特筆するべきことろはありません。
以上、Angularで構築した簡単な掲示板システムのご説明でした。今後はこのようなクライアントサイドフレームワークを用いた開発が主流になってくると思いますので、ぜひこれを機にAngularを触ってみて下さい。