0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-10-19

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

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

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に直接記述します。

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に以下を記述します。

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を追加します。

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を作成します。

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

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

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から修正します。

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を修正します。

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を修正します。

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

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

次回は登録・更新・削除・検索機能を実装していきます。
【Spring Boot + Angular + MySQLでWebアプリ作成】 CRUD編①へ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?