AngularでWEBアプリケーション作ってみる


初投稿です。JSPやサーブレットでの古いWEBアプリしか触ったことがないので
今時のWEBアプリの勉強ついでに書いてみます。


#Angular
Googleが開発しているフロントエンドJavaScriptフレームワーク。実装はTypeScriptで行う
MVC(Model View Controller) や MVW (Model-View-Whatever)モデルを構築できるらしい
ルーティングもやってくれるからこれだけで簡単にWEBアプリが作れる

##Angular CLI
Angular開発用のコマンドラインツール
Node.js上で動く(Node.js 6.9以上 npm 3.x以上)
各コンポーネントの自動生成やサーバーの起動などをやってくれる


## 初期構築/チュートリアル

###環境
- Windows10 pro
- PowerShell
- Node 10.16.3 LTS
- npm  6.9.0

###インストール
npmでAngular-cliを取得
オプション`-g`でグローバルインストール

```powershell:PowerShell
> npm install -g @angular/cli
```


###ワークスペースと初期アプリの構築
適当な作業ディレクトリに移動してAngula-cliのコマンドで初期構築

```powershell:PowerShell
> ng new my-app
```

`Would you like to add Angular routing? (y/N)`と聞かれるので`y`


必要なライブラリその他がインストールが終わったら`cd my-app`でディレクトリを移動する。
`ls`してみると色々とファイルが作成されている


/myapp
　  - src
 　 - .editorconfig
  　- .gitignore
  　- angular.json
  　- browserslist
  　- karma.conf.js
  　- package.json
  　- package-lock.json
  　- README.md
  　- tsconfig.app.json
  　- tsconfig.json
  　- tsconfig.spec.json
  　- tslint.json
  　- .git
  　- e2e
  　- node_modules

`ng serve`でWEBサーバを起動

```powershell:PowerShell
> ng serve
(中略)
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
i ｢wdm｣: Compiled successfully.
```

4200番ポートでサーバが起動したのでブラウザで開くとAngularなページが表示される
`localhost:4200`
![2019-10-06_12h14_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/ecaf9b73-838e-530f-a1ff-73ee516e0781.png)


##編集してみる

実体は`src/app/app.component.html`なので開いて編集する
*※既存のソースはすべて消去*

```html:src\app\app.component.html
<h1>AngularでWEBアプリケーションを作ってみる</h1>
```

保存すると`ng serve`のシェルで自動コンパイルが実行される

```powershell:PowerShell
i ｢wdm｣: Compiling...

Date: 2019-10-06T03:47:14.667Z - Hash: 14dbbd95520c919d3d31
4 unchanged chunks
chunk {main} main.js, main.js.map (main) 23.1 kB [initial] [rendered]
Time: 120ms
i ｢wdm｣: Compiled successfully.
```

ブラウザを見に行くとページが更新されている

|![2019-10-06_12h49_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/a8b7c387-41ae-6a21-ab71-5c62d0237909.png)|
|:-:|


###コンポーネントを追加
Angularはコンポーネントという画面を構成する部品を作って組み合わせる仕組みらしいので、
さっそく`ng generate component`してみる

*(`ng serve`で起動したプロセスはファイルの更新を検知して自動ビルド→デプロイをやってくれるようなので殺さず、
新しくコンソールを開いて作業する)*


```powershell:PowerShell
> ng g component header
CREATE src/app/header/header.component.html (21 bytes)
CREATE src/app/header/header.component.spec.ts (628 bytes)
CREATE src/app/header/header.component.ts (269 bytes)
CREATE src/app/header/header.component.css (0 bytes)
UPDATE src/app/app.module.ts (475 bytes)
> ng g component menu
CREATE src/app/menu/menu.component.html (19 bytes)
CREATE src/app/menu/menu.component.spec.ts (614 bytes)
CREATE src/app/menu/menu.component.ts (261 bytes)
CREATE src/app/menu/menu.component.css (0 bytes)
UPDATE src/app/app.module.ts (549 bytes)
> ng g component dashboard
CREATE src/app/dashboard/dashboard.component.html (24 bytes)
CREATE src/app/dashboard/dashboard.component.spec.ts (649 bytes)
CREATE src/app/dashboard/dashboard.component.ts (281 bytes)
CREATE src/app/dashboard/dashboard.component.css (0 bytes)
UPDATE src/app/app.module.ts (643 bytes)
```
*※`g` は `generate` の省略系*


`app.component.html`に生成したコンポーネントを記述

```html:src\app\app.component.html
<app-header></app-header>
<app-menu></app-menu>
<app-dashboard></app-dashboard>
```

保存すると、画面に反映される
**画面**

|![2019-10-06_13h21_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/ea851083-5705-3fb9-0986-ef2a854df9e2.png)|
|:-:|


##コンポーネントの編集

###ヘッダー
`src/app/header`に生成されたhtmlとcssを編集する

```html:src/app/header/header.component.html
<p>header works!</p>
```
　↓

```html:src/app/header/header.component.html
<div class="header">
  <img src="../favicon.ico" alt="Angular"> AngularでWEBアプリ作ってみた
</div>
```
*Angularに内蔵されているWEBサーバが元々持っているfaviconを利用*

```css:src/app/header/header.component.css
.header {
    padding: 4px;
    background-color: #29088A; /*背景は紺色*/
    color: #FFF;               /*文字は白色*/
  }
  .header img {
    width: 18px;
    vertical-align: middle;
  }
```

ページのヘッダー部分に自動的に反映される
**画面**

|![2019-10-06_13h48_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/9e6f19ef-dc1f-64e9-16ad-05bc3a0721e2.png)|
|:-:|

*(ブラウザF5は押していない。勝手に反映されてる。すごい)*

###メニュー
同様に`src/app/menu`のファイルを修正する

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

```css/app/menu/menu.component.css
.menu {
    background-color: #CED8F6; /*背景は薄青*/
  }
  .menu li {
    padding: 4px 8px;
    display: inline-block;
  }
```

###ダッシュボード
同様に`src/app/dashboard`も

```html:src/app/dashboard/dashboard.component.html
<h1>ダッシュボード</h1>
ここにいろいろ表示する
```



###編集後の画面
少しずつWebアプリっぽくなってきた
![2019-10-06_13h59_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/b3b071a5-9f4e-e047-bad4-be1b15ed912a.png)

##画面・機能を追加する

ここからは、Webアプリとしての機能を作る

無料で気象情報を提供してくれるAPIサービスのOpenweathermapを使って
登録した都市の天気を表示する機能を作ってみる

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

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

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

###ルーティング設定
公式サイトのチュートリアル‘は`ng generate module app-routing --flat --module=app`しろとあるが、既に`src/app/`に`app-routing-module.ts`があったのでそれを編集

```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';
import { CityEditComponent } from './city/city-edit/city-edit.component';

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

@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();
  }

}
```

###ダッシュボードに表示

`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との連携
登録時にOpenweathermapのAPIを叩いて天気を取得する仕組みを実装する


###HTTP通信用モジュールを追加
`app.module.ts`を編集し、HttpModuleを読み込む
ついでにこれから作成するHttp用サービス`HttpService`も追加しておく

```ts:src/app/app.module.ts
 :
 :
/* 追加 */
import { HttpClientModule } from '@angular/common/http';
import { HttpService } from './HttpService';

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

###HTTP通信の専用サービスを作成
共通なので`src/app/`直下に作成

```ts:src/app/HttpService.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class HttpService {

    constructor(private http: HttpClient) { };

    /* 受け取ったURLにリクエストを発行 */
    getWeatheritemsbyCity(apiEndPoint: string): Observable<any> {
        return this.http.get(apiEndPoint);
    };

}
```


###都市名サービスを修正
`city.service.ts`を修正

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

/* 追加*/
import { HttpService } from '../HttpService';


@Injectable({
  providedIn: 'root'
})
export class CityService {
  /* 追加 */
  environment = {
    production: false,
    appId: '<OpenweathermapのAPIキー>',
    baseUrl: 'http://api.openweathermap.org/data/2.5/'
  };

  cities: City[];

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

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

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

    /* OpenweathermapのAPIを呼び出す処理を追加 */
    //var result: any;
    var apiEndPoint: string = this.environment.baseUrl
    + 'weather?q=' + city.name
    + '&appid=' + this.environment.appId;

    this.service.getWeatheritemsbyCity(apiEndPoint)
    .subscribe(res => {
      var weather = res.weather[0].description;
      // 天気を設定して配列に追加
      city.weather = weather;
      this.cities.push(city);
    }, err => {
      console.log(err);
    }, () => {

    });
  }

  getCities(): City[]{
    return this.cities;
  }
}
```

これで一通り完成！

##動作確認
トップ画面から登録画面へ遷移して適当な都市の名前を登録
→ダッシュボードに天気が表示される

###ホーム画面(ダッシュボード)
![2019-10-06_23h30_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/f9d50212-3ad9-b93c-6c86-8fe40709aa8e.png)

###都市名登録画面
![2019-10-06_23h33_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/986a6aa7-c175-fcab-3082-1802db32017b.png)

###ホーム画面に戻る
![2019-10-06_23h34_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/ba2aa192-a994-d247-0d4d-efc005a93902.png)

###何件か登録してみる
福岡：晴れ
東京：曇り(千切れ雲)
高松：曇り(どんよりした雲)
![2019-10-06_23h36_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/d195c058-9cc1-b724-8c51-7b1823e883e9.png)


##さいごに
思ったより長くなってしまった。
かろうじて動く程度のアプリなので、時間を見つけて修正したい

###現状のまとめ

 **できたこと**
　・簡単な画面遷移(初期→登録→初期)
　・フォームでの値の入力
　・登録結果の一覧表示
　・外部APIとの連携

 **できてないこと**
　・登録結果の保存(WEBサーバを再起動したら消える)
　・登録内容の編集
　・エラー処理

###参考にしたサイト
　以下のページを参考にさせていただきました。大変感謝です。

　・[Angular公式](https://angular.io/)
　・[とほほのWWW入門 - Angular入門](http://www.tohoho-web.com/ex/angular.html)
　・[How To Use the OpenWeatherMap API to Make a Weather App in Angular](https://medium.com/better-programming/how-to-use-the-openweathermap-api-to-make-a-weather-app-f8d67b22c3ca)

　（長くなりすぎたので記事分割しようかな、、、）