Angular+Openweathermap+Leafret.js+MongoDBで天気予報WEBアプリを作る ②コンポーネントとサービスの追加(登録画面)



##今回やること
　前回はヘッダー/メニュー/ダッシュボード(メイン画面)を作ったので、今回は登録用の画面と処理を作る
　登録画面コンポーネントの追加と都市情報の管理を行うサービスを構築する
　※前回　[①Angular環境構築とひな形の生成](https://qiita.com/ryo_1050/items/71f0d5dd56b121c64b84)

![システム構成_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/2fd241ba-ff98-e4fe-fa8c-ba67f60cc385.png)
　　※オレンジ枠の部分

##画面・機能を追加する
　「①Angular環境構築とひな形の生成」で作ったシンプルなWEBアプリケーションに要素を追加してゆく

###コンポーネントを追加

`ng g component`で登録画面用と編集画面用のコンポーネントを生成する

```powershell:PowerShell
> ng g component city/city-add
```

###ルーティング設定
`src/app/`の`app-routing-module.ts`を編集
`http://localhost:3000/city/add`とimportした`CityAddComponent`を紐づける

```ts:src/app/app-routing-module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

/* 追加　*/
import { DashboardComponent } from './dashboard/dashboard.component';
import { CityAddComponent } from './city/city-add/city-add.component';

const routes: Routes = [
  /* 追加　*/
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'city/add', component: CityAddComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

トップ画面のHTMLを修正してルーティング用コンポーネントを指定

```html:src\app\app.component.html
<app-header></app-header>
<app-menu></app-menu>
<div class="main">
    <router-outlet></router-outlet>
</div>
```

###リンクの作成
メニュー画面を修正して都市名登録画面へのリンクを作成する
ついでにダッシュボードへのリンクも`<a href>`から`<a routerLink>`に変えておく

```html:src\app\menu.component.html
<div class="menu">
    <ul>
    <li><a href="/">Dashboard</a></li>
    <li><a routerLink="/city/add">city-add</a></li>
    </ul>
</div>
```

メニューのリンクを押下するとcity-addコンポーネントが動作するようになる

**画面**

![2019-10-06_15h06_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/9be07484-5036-47a9-cacf-cbc7ee8c76a2.png)

###都市オブジェクトの作成
都市名を登録するためのオブジェクト(クラス)を`src/app/city`に作成する

```ts:src/app/city/city.ts
export class City {
    id: number;       /* 連番 */
    name: string;     /* 都市名 */
    weather: string;  /* 天気を取得して格納する */
  }
```


###サービスを作成
`ng g service`コマンドでサービスを生成する

```powershell:PowerShell
> ng g service city/city
```

`app.module.ts`にサービスを追加する

```ts:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 :
 :
/* 追加 */
import { CityService } from './city/city.service';
 :
 :
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [
    /* 追加 */
    CityService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```



サービスに登録処理/取得処理を実装する

```ts:src/app/city/city.service.ts
import { Injectable } from '@angular/core';
/* 追加 */
import { City } from './city';

@Injectable({
  providedIn: 'root'
})
export class CityService {
  /* 追加 */
  cities: City[];

  constructor() {
    /* 追加 */
    this.cities = [];
 }

  /* 追加 */
  addCity(city: City): void {
    var id: number;
    id = this.cities.length + 1;

    /* idを設定 */
    city.id = id;

    /* 配列に追加 */
    this.cities.push(city);
  }

  /* 追加 */
  getCities(): City[]{
    return this.cities;
  }
}
```

###登録用の画面を作成

`app.modules.ts`にフォーム用部品`FormsModule`を組み込む

```ts:src/app/app.module.ts
 :
 :
/* 追加 */
import { FormsModule } from '@angular/forms';

@NgModule({
 :
 :
  imports: [
    BrowserModule,
    AppRoutingModule,
    /* 追加 */
    FormsModule
  ],
 :
 :
```

`city-add.component.html`を編集して登録用のフォームを作成する

```html:src/app/city/city-add.component.html
<p>city-add works!</p>
<form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
    <input type="text" name="name" [(ngModel)]="name">
    <button type="submit">OK</button>
    <button routerLink="/dashboard">Cancel</button>
</form>

```

`city-add.component.ts`に登録処理の呼び出しを実装

```ts:src/app/city/city-add.component.ts
import { Component, OnInit } from '@angular/core';
/* 追加 */
import { Router } from '@angular/router';
import { City } from './city';
import { CityService } from '../city.service';

@Component({
  selector: 'app-city-add',
  templateUrl: './city-add.component.html',
  styleUrls: ['./city-add.component.css']
})
export class CityAddComponent implements OnInit {

  constructor(
    /* 追加 */
    private router: Router,
    private service: CityService
  ) { }

  ngOnInit() {
  }

  /* 追加 */
  onSubmit(form: any): void {
    // フォームの値を取得
    let name = form.name;
    // Cityクラスのインスタンスを生成して名前を指定
    var city: City = new City;
    city.name = name;
    // 天気はとりあえず'-'を設定
    city.weather = '';

    // サービスの都市登録メソッドに渡す
    this.service.addCity(city);

    // ダッシュボードに遷移
    this.router.navigate(['dashboard']);
  }
}
```

これでいったん登録処理は完成

**画面**


![2019-10-06_16h41_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/3e4b6ca9-35a7-9f78-1787-a08bf7761aa7.png)

##ダッシュボードに登録内容を表示

`city.service.ts`に登録内容が保持されているはずなので、ダッシュボードで表示してみる

###都市一覧を取得

`dashboard.component.ts`にサービスから値を取得する処理を実装する

```ts:src/app/dashboard/dashboard.component.ts
import { Component, OnInit } from '@angular/core';
/* 追加 */
import { ActivatedRoute, Router } from '@angular/router';
import { City } from '../city/city';
import { CityService } from '../city/city.service';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  /* 追加 */
  cities: City[];

  constructor(
    /* 追加 */
    private router: Router,
    private service: CityService
  ){ } 

  /* 追加 */
  ngOnInit() {
    this.cities = this.service.getCities();
  }

}
```

###表示用のHTML

`dashboard.component.html`を編集して表示の仕組みを作る
*(\*ngIfを使って登録件数が0以外の場合のみテーブル作成）*

```html:src/app/dashboard/dashboard.component.html
<h1>ダッシュボード</h1>
ここにいろいろ表示する
<div *ngIf="cities.length !== 0">
    <br>
    <table>
        <tr><th>Id</th><th>Name</th><th>Weather</th></tr>
        <tr *ngFor="let city of cities">
            <td>{{city.id}}</td>
            <td>{{city.name}}</td>
            <td>{{city.weather}}</td>
        </tr>
    </table>
</div>
```

**画面**

適当に登録
![2019-10-06_17h37_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/40736273-9de4-fe08-6845-b2c92f738b5a.png)

OKボタンでダッシュボードに遷移
![2019-10-06_17h38_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/3d3a23bf-fde0-5967-c331-9d3a085b96f4.png)


やったぜ
とりあえずこれで登録処理完了(メモリ上だけで保存はしていない)

##次回の予定
　外部APIを実行するためのHttp通信用のサービス構築
　OpenweathermapのAPIを実行して都市の天気を取得

###追記
　　[①Angular環境構築とひな形の生成](https://qiita.com/ryo_1050/items/71f0d5dd56b121c64b84)
　　②コンポーネントとサービスの追加(登録画面)　
　　[③OpenweathermapのAPIを叩く](https://qiita.com/ryo_1050/items/578903a80653c92aa4cf)
　　[④Leafletで地図を表示する](https://qiita.com/ryo_1050/items/663eba44afe7ee6d95a1)
　　[⑤サービスが持つプロパティの更新を検知する](https://qiita.com/ryo_1050/items/eca6a032d714a4e24f8b)