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

##Angular側

CRUD編①で作成したSpring Bootのバックエンドと通信を行う画面を作成します。


##DTO追加
バックエンドから返却されるデータを格納したり、バックエンドにパラメータを渡すためのDTOを追加します。


```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>
}

/* 以下追加 */

/**
 * 検索パラメータ格納用
 */
export class HomeSearchParamDto {
  id: number;
  name: string;
  fromDate: String;
  toDate: String;
  departmentId: number;
}

/**
 * 検索結果格納用
 */
export class HomeSearchResultDto {
  employeeList: Array<EmployeeDepartment>
}

/**
 * 登録パラメータ格納用
 */
export class HomeRegistParamDto {
  employee: Employee;
}

/**
 * 登録結果格納用
 */
export class HomeRegistResultDto {
  status: boolean;
}

```

##Service修正

バックエンドに登録・更新・削除・検索する処理を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, HomeSearchParamDto, HomeSearchResultDto, HomeRegistParamDto, HomeRegistResultDto, Employee } 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');
  }

  /**
   * 検索
   * @param searchParamDto 
   */
  homeFindByParams(searchParamDto: HomeSearchParamDto) {
    return this.http.post<HomeSearchResultDto>(this.api + '/Home/search', searchParamDto);
  }

  /**
   * 登録
   * @param registParam 
   */
  homePost(registParam: HomeRegistParamDto) {
    return this.http.post<HomeRegistResultDto>(this.api + '/Home/', registParam);
  }


  /**
   * 更新
   * @param employee 
   */
  putUser(employee: Employee) {
    return this.http.put<HomeRegistResultDto>(this.api + '/Home/', employee);
  }


  /**
   * 削除
   * @param deleteIdList 
   */
  homeDelete(deleteIdList: Array<number>) {
    this.httpOptions.body = deleteIdList;
    return this.http.delete<Array<number>>(this.api + '/Home/', this.httpOptions);
  }
}
```


##登録
新規登録ボタンを押したときに登録ダイアログを表示させます。
新規登録用のダイアログを作成します。

```
ng g c employee-create-dialog
```

MatDialogを使用するのでhome.component.tsのコンストラクタを以下のように修正します。

```typescript

constructor(private apiService: ApiService, private readonly dialog: MatDialog) { }

```

home.component.tsのonCreate()メソッドに新規登録ボタンが押された際の処理を記述します。

```typescript:home.component.ts
  /**
   * 新規作成ボタン押下
   */
  onCreate() {
    let employee = new Employee();
    const dialogRef = this.dialog.open(EmployeeCreateDialogComponent, {
      'width': '600px',
      data: { employee: employee, departmentList: this.departmentList }
    });
    dialogRef.afterClosed().subscribe(result => {

      console.log(result.birthday);
      let registParam: HomeRegistParamDto = new HomeRegistParamDto();
      if (result) {
        registParam.employee = result;
        this.apiService.homePost(registParam).subscribe(
          x => { this.onSearch(); }
        );
      }
    });
  }
```
ここまでで、新規登録ボタンを押すとダイアログが表示されるようになります。

###新規登録ダイアログ作成
新規登録ダイアログの画面を作成します。

を以下のように記述します。

```typescript

import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Department, Employee } from '../home/home.component';

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

  constructor(public dialogRef: MatDialogRef<EmployeeCreateDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: { employee: Employee, departmentList: Array<Department> }) { }

  ngOnInit(): void {
  }

  onChange(deviceValue) {
    this.data.employee.department_id = deviceValue;
  }

}

```

htmlを以下のように修正します。

```html

<h2 mat-dialog-title>新規登録</h2>
<mat-dialog-content>
    <table width="80%" style="margin: 0 auto;">
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                社員ID
            </th>
            <td>
                自動採番
            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                社員名
            </th>
            <td>
                <input type="text" class="input" [(ngModel)]="data.employee.name">
            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                部署
            </th>
            <td>
                <select id="language" name="language" class="input" (change)="onChange($event.target.value)">
                    <option disabled="disabled" selected [value]="">選択してください</option>
                    <option *ngFor="let department of data.departmentList" [value]="department.id">
                        {{department.name}}</option>
                </select>
            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                生年月日
            </th>
            <td>
                <input type="date" class="input" [(ngModel)]="data.employee.birthday">
            </td>
        </tr>
    </table>
</mat-dialog-content>
<mat-dialog-actions align="end">
    <button mat-raised-button mat-dialog-close>キャンセル</button>
    <button mat-raised-button [mat-dialog-close]="data.employee">登録</button>
</mat-dialog-actions>
```


##更新
行を選択したときに編集ダイアログを表示させます。
編集用のダイアログを作成します。

```
ng g c employee-edit-dialog
```

home.component.tsのrowClick()メソッドに以下を追記します。

```typescript

  /**
   * 行選択
   * @param employee 
   */
  rowClick(employee: EmployeeDepartment): void {
    const dialogRef = this.dialog.open(EmployeeEditDialogComponent, {
      'data': { employee: employee, departmentList: this.departmentList, selectedName: employee.departmentName },
      'width': '600px'
    });
    dialogRef.afterClosed().subscribe(result => {

      if (result) {

        let employee: Employee = new Employee();
        employee.id = result.id;
        employee.name = result.employeeName;
        let x = this.departmentList.find(x => x.name == result.departmentName);
        employee.department_id = x.id;
        employee.birthday = result.birthday;

        this.apiService.putUser(employee).subscribe(
          x => { this.onSearch(); }
        );
      }
    });
  }
```

###編集ダイアログ作成

```typescript
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Department, EmployeeDepartment } from '../home/home.component';

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

  constructor(public dialogRef: MatDialogRef<EmployeeEditDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: { employee: EmployeeDepartment, departmentList: Array<Department>, selectedName: string }) { }

  ngOnInit(): void {
  }

  onChange(deviceValue) {
    this.data.employee.departmentName = deviceValue;
    console.log(deviceValue)
  }

}
```


html修正

```html
<h2 mat-dialog-title>編集</h2>

<mat-dialog-content>

    <table width="80%" style="margin: 0 auto;">
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                社員ID
            </th>
            <td>
                {{data.employee.employeeId}}
            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                社員名
            </th>
            <td>
                <input type="text" class="input" [(ngModel)]="data.employee.employeeName">
            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                部署
            </th>
            <td>

                <select id="language" name="language" class="input" (change)="onChange($event.target.value)">
                    <option *ngFor="let department of data.departmentList" [value]="department.name"
                        [selected]="department.name == data.selectedName">
                        {{department.name}}</option>
                </select>

            </td>
        </tr>
        <tr>
            <th style="background-color: lightgreen; font-weight: lighter;">
                生年月日
            </th>
            <td>
                <input type="date" class="input" [(ngModel)]="data.employee.birthday">
            </td>
        </tr>
    </table>

</mat-dialog-content>
<mat-dialog-actions align="end">
    <button mat-raised-button mat-dialog-close>キャンセル</button>
    <button mat-raised-button [mat-dialog-close]="data.employee">更新</button>
</mat-dialog-actions>

```

これで行をクリックすると編集ダイアログが表示されるようになります。


##削除

削除処理は、テーブルに新しくチェックボックスのカラムを追加し、複数行をまとめて削除できるようにします。

home.component.tsのonDelete()メソッドを修正します。



```typescript

  /**
   * 削除ボタン押下
   */
  onDelete() {
    if (this.selection.selected.length == 0) {
      alert("削除するユーザーを選択してください")
      return;
    }

    let deleteIdList: Array<number> = this.selection.selected.map(x => x.employeeId);

    this.apiService.homeDelete(deleteIdList).subscribe(
      x => { this.onSearch(); }
    );
  }
```




##検索

最後に検索機能を実装します。

```typescript
  /**
   * 検索ボタン押下
   */
  onSearch() {
    // 検索パラメータ作成
    let searchParamDto: HomeSearchParamDto = new HomeSearchParamDto();

    if (this.searchId.length == 0) {
      searchParamDto.id = 0;
    } else {
      searchParamDto.id = Number(this.searchId);
    }
    searchParamDto.name = this.searchName;
    searchParamDto.fromDate = this.searchFromDate;
    searchParamDto.toDate = this.searchToDate;
    searchParamDto.departmentId = Number(this.searchDepartmentIndex);

    this.apiService.homeFindByParams(searchParamDto).subscribe(
      result => {
        this.dataSource.data = result.employeeList;
        console.log(JSON.stringify(result.employeeList));
        this.selection.clear()
      }
    )
  }
```

以上で登録・更新・削除・検索の全機能の実装が完了しました。

##アプリケーション完成
アプリケーションを実行し、各機能が正しく動作することを確認してください。

```
npm start
```