【Spring Boot + Angular + MySQLでWebアプリ作成】 Angular編②

今回はSpring Bootで作成したバックエンドのと通信を行い、画面に社員情報一覧を表示させます。

まずはサーバと通信するための準備を行います。
app.module.tsにHttpClientModuleをインポートします。

```typescript: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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './material/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import { HomeComponent } from './home/home.component';
import { HttpClientModule } from '@angular/common/http'; //追加

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

##Dto作成
バックエンドと通信した際に返る値を格納するためのDtoを作成します。
本来はフォルダを分けるべきかもしれませんが、今回はhome.component.tsに直接記述します。

```typescript:home.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

/**
 * 社員
 */
export class Employee {
  id: number;
  name: string;
  department_id: number;
  birthday: Date;
}

/**
 * 部署
 */
export class Department {
  id: number;
  name: string;
}

/**
 * 検索結果格納用
 */
export class EmployeeDepartment {
  employeeId: number;
  employeeName: string;
  departmentName: string;
  birthday: string;
}

/**
 * 初期処理結果格納用
 */
export class HomeInitResultDto {
  employeeList: Array<EmployeeDepartment>
  departmentList: Array<Department>
}

```

##Service作成
バックエンドと通信を担当するServiceを作成します

```
ng g s api
```

作成されたapi.service.tsに以下を記述します。

```typescript:api.service.ts
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HomeInitResultDto } from './home/home.component';

@Injectable({
  providedIn: 'root'
})
export class ApiService {

  constructor(private http: HttpClient) { }

  private api: string = '/api'

  private httpOptions: any = {

    headers: new HttpHeaders({
      'Content-Type': 'application/json'
    }),
    body: null
  };

  /**
   * 初期化
   */
  homeInit(): Observable<HomeInitResultDto> {
    return this.http.get<HomeInitResultDto>(this.api + '/Home/init');
  }
}
```

##FormsModule追加

mgModelを使用するのでapp.module.tsにFormsModuleを追加します。

```typescript: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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './material/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import { HomeComponent } from './home/home.component';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms'; //追加

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

##リバースプロキシ設定

今回作成するアプリはフロントエンドとバックエンドのポート番号が違うため、CORSが発生し通信が失敗します。
そのためリバースプロキシの設定を行います。
Angularプロジェクトのexample直下にproxy.conf.jsonを作成します。


```json:proxy.conf.json
{
    "/api": {
        "target": "http://localhost:8080",
        "pathRewrite": {
            "^/api": ""
        }
    }
}
```


package.jsonにproxy.confを使う設定を追記します。

```json:packeage.json
{
  "name": "example",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --proxy-config proxy.conf.json", // 追加
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~9.1.13",
    "@angular/cdk": "^9.2.4",
    "@angular/common": "~9.1.13",
    "@angular/compiler": "~9.1.13",
    "@angular/core": "~9.1.13",
    "@angular/forms": "~9.1.13",
    "@angular/material": "^9.2.4",
    "@angular/platform-browser": "~9.1.13",
    "@angular/platform-browser-dynamic": "~9.1.13",
    "@angular/router": "~9.1.13",
    "rxjs": "~6.5.4",
    "tslib": "^1.10.0",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.901.13",
    "@angular/cli": "~9.1.13",
    "@angular/compiler-cli": "~9.1.13",
    "@types/node": "^12.11.1",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "^5.1.2",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.0",
    "karma-jasmine": "~3.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~3.8.3"
  }
}
```

これでリバースプロキシの設定は完了です。

##画面作成


社員一覧画面を作成します。
まずはhome.component.tsから修正します。


```typescript:home.component.ts
import { SelectionModel } from '@angular/cdk/collections';
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { ApiService } from '../api.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  constructor(private apiService: ApiService) { }

  employeeList: Array<Employee> = [];
  departmentList: Array<Department> = [];
  searchId: string = "";
  searchName: string = "";
  searchFromDate: string = "";
  searchToDate: string = "";
  panelOpenState = false;
  displayedColumns: string[] = ['select', 'id', 'name', 'department', 'birthday'];
  dataSource = new MatTableDataSource<EmployeeDepartment>();
  selection = new SelectionModel<EmployeeDepartment>(true, []);

  searchDepartmentIndex = "0";

  /**
   * 初期処理
   */
  ngOnInit(): void {
    this.apiService.homeInit().subscribe(
      result => {
        this.dataSource.data = result.employeeList;
        this.departmentList = result.departmentList;
        console.log(JSON.stringify(this.dataSource.data))
      }
    )
  }

  /**
   * 検索ボタン押下
   */
  onSearch() {

  }

  /**
   * リセットボタン押下
   */
  onReset() {

    this.searchId = "";
    this.searchName = "";
    this.searchFromDate = "";
    this.searchToDate = "";
    this.onSearch();
  }

  /**
   * 行選択
   * @param employee 
   */
  rowClick(employee: EmployeeDepartment): void {

  }

  /**
   * 削除ボタン押下
   */
  onDelete() {

  }

  onChange(deviceValue) {
    this.searchDepartmentIndex = deviceValue;
    console.log(deviceValue)
  }

  /**
   * 新規作成ボタン押下
   */
  onCreate() {
   
  }

  /** Whether the number of selected elements matches the total number of rows. */
  isAllSelected() {
    const numSelected = this.selection.selected.length;
    const numRows = this.dataSource.data.length;
    return numSelected === numRows;
  }

  /** Selects all rows if they are not all selected; otherwise clear selection. */
  masterToggle() {
    this.isAllSelected() ?
      // チェック開放
      this.selection.clear() :
      // 全部にチェック
      this.dataSource.data.forEach(row => this.selection.select(row));
  }

  /** The label for the checkbox on the passed row */
  checkboxLabel(row?: EmployeeDepartment): string {
    if (!row) {
      return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
    }
    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.employeeId + 1}`;
  }

}

/**
 * 社員
 */
export class Employee {
  id: number;
  name: string;
  department_id: number;
  birthday: Date;
}

/**
 * 部署
 */
export class Department {
  id: number;
  name: string;
}

/**
 * 検索結果格納用
 */
export class EmployeeDepartment {
  employeeId: number;
  employeeName: string;
  departmentName: string;
  birthday: string;
}

/**
 * 初期処理結果格納用
 */
export class HomeInitResultDto {
  employeeList: Array<EmployeeDepartment>
  departmentList: Array<Department>
}

```

次にhome.component.htmlを修正します。

```typescript:home.component.html
<div fxFill fxLayout="row">
    <div fxFlex>
        <div>
            <mat-card style="margin: 10px 10px 10px 10px;" class="mat-elevation-z5">
                <table width="80%" style="margin: 0 auto;">
                    <tr>
                        <th style="background-color: lightgreen; font-weight: lighter;">
                            社員ID
                        </th>
                        <td align="left"><input type="text" class="input" maxlength="5" [(ngModel)]="searchId"
                                oninput="value = value.replace(/[^0-9]+/i,'');"></td>
                        <th style="background-color: lightgreen; font-weight: lighter;">
                            社員名
                        </th>
                        <td align="left"><input type="text" class="input" [(ngModel)]="searchName"></td>
                    </tr>
                    <tr>
                        <th style="background-color: lightgreen; font-weight: lighter;">
                            部署
                        </th>
                        <td align="left">

                            <select id="language" name="language" class="input">
                                <option disabled="disabled" selected [value]="">選択してください</option>
                                <option *ngFor="let department of departmentList" [value]="department.id">
                                    {{department.name}}</option>
                            </select>
                        </td>
                        <th style="background-color: lightgreen; font-weight: lighter;">
                            生年月日
                        </th>
                        <td align="left"><input type="date" class="input" [(ngModel)]="searchFromDate">~
                            <input type="date" class="input" [(ngModel)]="searchToDate">
                        </td>
                    </tr>
                </table>
                <div style="width: 400px; margin-left: auto;">
                    <button mat-raised-button (click)="onSearch()">
                        <mat-icon>search</mat-icon>検索
                    </button>
                    <button mat-raised-button (click)="onReset()">
                        <mat-icon>refresh</mat-icon>リセット
                    </button>
                    <button mat-raised-button (click)="onDelete()">
                        <mat-icon>delete</mat-icon>削除
                    </button>
                    <button mat-raised-button (click)="onCreate()">
                        <mat-icon>create</mat-icon>新規作成
                    </button>
                </div>
            </mat-card>
        </div>
        <div>
            <mat-card style="margin: 10px" class="mat-elevation-z5">
                <table mat-table [dataSource]="dataSource" fxFlexFill>

                    <ng-container matColumnDef="select">
                        <th mat-header-cell *matHeaderCellDef style="width: 80px;">
                            <mat-checkbox (change)="$event ? masterToggle() : null"
                                [checked]="selection.hasValue() && isAllSelected()"
                                [indeterminate]="selection.hasValue() && !isAllSelected()"
                                [aria-label]="checkboxLabel()">
                            </mat-checkbox>
                        </th>
                        <td mat-cell *matCellDef="let row">
                            <mat-checkbox (click)="$event.stopPropagation()"
                                (change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)"
                                [aria-label]="checkboxLabel(row)">
                            </mat-checkbox>
                        </td>
                    </ng-container>

                    <ng-container matColumnDef="id">
                        <th mat-header-cell *matHeaderCellDef>社員Id</th>
                        <td mat-cell *matCellDef="let element"> {{element.employeeId}} </td>
                    </ng-container>

                    <ng-container matColumnDef="name">
                        <th mat-header-cell *matHeaderCellDef>社員名</th>
                        <td mat-cell *matCellDef="let element"> {{element.employeeName}} </td>
                    </ng-container>

                    <ng-container matColumnDef="department">
                        <th mat-header-cell *matHeaderCellDef>部署</th>
                        <td mat-cell *matCellDef="let element"> {{element.departmentName}} </td>
                    </ng-container>

                    <ng-container matColumnDef="birthday">
                        <th mat-header-cell *matHeaderCellDef>生年月日</th>
                        <td mat-cell *matCellDef="let element"> {{element.birthday | date:"yyyy年MM月dd日"}} </td>
                    </ng-container>

                    <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"
                        style="background-color: lightgreen;">
                    </tr>
                    <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="rowClick(row)"></tr>
                </table>
            </mat-card>
        </div>
    </div>
</div>
```

style.scssを修正します。

```scss:styles.scss
/* You can add global styles to this file, and also import other style files */

html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

.header{
    height: 50px;
    background-color: lightgreen;
}

.footer{
    background-color: lightgreen;
    height: 30px;
    text-align: center;
}

// 追加
.input{
    border:0;
    padding:5px;
    font-size:1em;
    font-family:Arial, sans-serif;
    color:rgb(82, 79, 79);
    border:solid 1px #ccc;
    margin:0 0 10px;
    width:200px;

    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
}

// 追加
.mat-row:hover {
    background-color: lightgray;
    cursor : pointer;
}
```

##アプリケーション実行

以下のコマンドを入力してアプリケーションを起動します。

```
npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425239/514959b9-26c4-d58f-3b5c-715a6d7a25bf.png)


このような画面が表示されたらOKです。

次回は登録・更新・削除・検索機能を実装していきます。
[【Spring Boot + Angular + MySQLでWebアプリ作成】 CRUD編①へ](https://qiita.com/k-yamada-neut/private/0ed75ecb3e1df9bffe2a)