JavaScriptフレームワーク「Angular」による掲示板システム構築

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

こんにちは。サイオステクノロジー技術部 武井です。

今回は、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

アプリケーションの画面は以下のとおりです。

まずログイン画面です。

Screen Shot 2017-12-20 at 22.56.57

ログインすると、メッセージの一覧画面が表示されます。新規登録ボタンをクリックすると、新規にメッセージを登録する画面に遷移します。タイトルをクリックするとメッセージ変更画面に遷移し、削除をクリックするとメッセージが削除されます。

Screen Shot 2017-12-20 at 23.00.01

メッセージの新規登録画面です。

Screen Shot 2017-12-20 at 23.00.14

メッセージの変更画面です。

Screen Shot 2017-12-20 at 23.00.25

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で取得したアクセストークン]

–<リクエストボディ>–
なし

–<レスポンスボディ>–
なし

アプリケーションのディレクトリ構成は以下のようになっております。

Screen Shot 2017-12-21 at 9.38.35

では、それぞれのソースについて説明いたします。

■ 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を触ってみて下さい。

アバター画像
About 武井 宜行 269 Articles
Microsoft MVP for Azure🌟「最新の技術を楽しくわかりやすく」をモットーにブログtech-lab.sios.jp)で情報を発信🎤得意分野はAzureによるクラウドネイティブな開発(Javaなど)💻「世界一わかりみの深いクラウドネイティブ on Azure」の動画を配信中📹 https://t.co/OMaJYb3pRN
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

3人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる