Angular入門：APIのとのやり取り


AngularはSPAのフレームワークなので動的処理は基本的にサーバ側APIとのやり取りになります。
ここでは簡単なCRUD処理を試してみます。

## 簡単なAPIサーバの実装

まず、expressを利用して簡単なテスト用APIサーバを実装します。

>Angular-Inmemory-Web-Apiという仕組みもありますが、ここではexpressを利用します。

### 作用場の確保と必要モジュールのインストール

作業場を用意して実装ファイルであるindex.jsを作成します。

```bash
mkdir rest-server
cd rest-server
npm init -y
npm install express
touch index.js
```

### 実装

index.jsに対し、下記のような実装をします。

>DBは利用せずメモリ内の配列を利用します（データはサーバを落とせば永続化されません）。

```js:index.js
var express = require('express');
var app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

//CROS対応（何でも許可：実運用ではこんなことしちゃだめ）
app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "*")
    res.header("Access-Control-Allow-Headers", "*");
    next();
});

//サーバ起動
app.listen(3000, function () {
    console.log("Start on port 3000.")
});

//サンプルデータ
let users = [
    { id: 1, name: "User1", email: "user1@test.local" },
    { id: 2, name: "User2", email: "user2@test.local" },
    { id: 3, name: "User3", email: "user3@test.local" }
];

//一覧取得
app.get('/users', function (req, res) {
    res.send(JSON.stringify(users));
});

//追加
app.post('/users', function (req, res) {
    users.push(req.body);
    res.send(JSON.stringify({ status: "OK" }))
});

//idで指定したuserを取得
app.get('/users/:id', function (req, res) {
    for (let i = 0; i < users.length; i++) {
        if (users[i].id == req.params.id) {
            res.send(JSON.stringify(users[i]));
        }
    }
});

//指定したidを更新
app.post('/users/:id', function (req, res) {
    for (let i = 0; i < users.length; i++) {
        if (users[i].id == req.params.id) {
            users[i] = req.body;
        }
    }
    res.send(JSON.stringify({ status: "OK" }))
});

//削除
app.delete('/users/:id', function (req, res) {
    for (let i = 0; i < users.length; i++) {
        if (users[i].id == req.params.id) {
            users.splice(i, 1);
        }
    }
    res.send(JSON.stringify({ status: "OK" }))
})
```

### 実行

実装したら実行して動作を確認します。

```bash
node index.js
```

>終了はctr + cで。

### テスト

curlを使って動作チェックをしておきます。

```bash
#全件取得
curl -s -X GET http://localhost:3000/users | python -mjson.tool
#指定ID取得
curl -s -X GET http://127.0.0.1:3000/users/1 | python -mjson.tool
#追加
curl -s -X POST -H 'Content-Type: application/json' -d '{"id":4,"name":"User4","email":"user4@test.local"}' http://127.0.0.1:3000/users
#更新
curl -s -X POST -H 'Content-Type: application/json' -d '{"id":4,"name":"User4_Updated","email":"user4@test.local"}' http://127.0.0.1:3000/users/4
#削除
curl -s -X DELETE http://127.0.0.1:3000/users/4
```

## クライアント（Angular）側実装

APIの動作チェックが終わったらAngularでクライアント側を実装します。

### 完成イメージ


下記のようなテスト用の画面を実装します。

![スクリーンショット 2021-01-27 8.54.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/48494ffe-73c4-750f-b9ef-84230adf73d3.png "スクリーンショット 2021-01-27 8.54.14.png")


### 作業場の準備

まずは作業場を作成

```bash
ng new api-client
cd api-client
```

### serviceの作成

通信処理を実装するためのserviceを生成します。名前は何もいいですがdataとしました。

```bash
ng generate service data
```

### モデルの作成

今回はForm処理にテンプレート駆動型Formを利用するのでモデルを生成しておきます。場所はapp直下にします。

```bash
touch user.ts
```

user.tsに下記を記述します。

```ts:app/user.ts
export class User {
    id: number;
    name: string;
    email: string;
}
```

### app.module.ts

続いて上記サービスの他、必要なモジュールを読み込みます。

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

//追加
import { DataService } from './data.service';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

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

### data.service.ts

サービスを実装します。

```ts:data.service.ts
import { Injectable } from '@angular/core';

//追加
import { User } from './user';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class DataService {

  users: User[];
  private url = "http://127.0.0.1:3000";

  constructor(
    private http: HttpClient
  ) { }

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(`${this.url}/users`);
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.url}/users/${id}`)
  }

  setUser(user: User): Observable<User> {
    return this.http.post<User>(`${this.url}/users`, user);
  }

  updateUser(user: User): Observable<User> {
    return this.http.post<User>(`${this.url}/users/${user.id}`, user);
  }

  deleteUser(user: User): Observable<User> {
    return this.http.delete<User>(`${this.url}/users/${user.id}`);
  }
}
```


>エラー処理は省いてます。

### app.component.ts

続いてcomponent.tsにサービスを呼び出しを実装します。今回はapp.component.tsに直接書いています。

```ts:app.component.ts
import { Component } from '@angular/core';

//追加
import { User } from './user';
import { DataService } from './data.service';
import { NgForm } from '@angular/forms';

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

  title = 'rest-client';

  users: User[];
  user: User = { id: 4, name: 'User4', email: 'user4@test.local' };
  form: NgForm;

  constructor(
    private service: DataService
  ) { }

  getUsers(): void {
    this.service.getUsers().subscribe(res => {
      this.users = res;
    })
  }

  getUser(id: number): void {
    this.service.getUser(id).subscribe(res => {
      console.log(JSON.stringify(res));
    })
  }

  setUser(form: User): void {
    this.service.setUser(form).subscribe(res => {
      console.log(res);
    })
  }

  updateUser(form: User): void {
    this.service.updateUser(form).subscribe(res => {
      console.log(res);
    })
  }

  deleteUser(form: User): void {
    this.service.deleteUser(form).subscribe(res => {
      console.log(res);
    })
  }
}
```

### app.component.html

最後にHTMLを仕上げます。

```html:app.component.html
<h3>取得</h3>
<button (click)="getUsers()">データ取得</button>
<ul *ngFor="let user of users">
  <li>{{user.id}} {{user.name}}</li>
</ul>
<h3>追加</h3>
<form #form=ngForm (ngSubmit)="setUser(form.value)">
  id:<input type="text" value="4" name="id" [ngModel]="user.id"><br>
  name:<input type="text" value="sasaki" name="name" [ngModel]="user.name"><br>
  email:<input type="text" value="sasaki@test.com" name="email" [ngModel]="user.email"><br>
  <button>登録</button>
</form>
<h3>編集</h3>
<button (click)="updateUser(form.value)">編集</button>
<h3>削除</h3>
<button (click)="deleteUser(form.value)">削除</button>
<h3>取得</h3>
<button (click)="getUser(1)">取得（１）</button>
```

### 動作確認

動作確認をします。

```bash
ng serve --open
```

## おまけ

### headerをカスタマイズする

下記はmicroCRMのAPIにリクエストを投げている例。

```ts:data.service.ts
import { Injectable } from '@angular/core';

//追加
import { User } from './user';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class DataService {

  users: User[];
  private url = "http://127.0.0.1:3000";

  //Header
  private httpOptions: any = {
    headers: new HttpHeaders({
      'X-API-KEY': 'xxxxxxx-fbc3-49ac-a1dd-xxxxxxxxxx' //keyは変えてね
    }),
    body: null
  }

  constructor(
    private http: HttpClient
  ) { }


  getLogo(): Observable<any> {
    return this.http.get<any>(`https://etco.microcms.io/api/v1/logo`, this.httpOptions);
  }
}

```