【Angular v19 + SpringBoot 09】検索機能の追加

【スキル】エンジニア
たけ坊
たけ坊

ここからは機能追加を行っていくよ!

バージョンアップしてくんですね!

前提

これまでの流れはこちらから!

実装

検索機能を追加するために、searchコンポーネントを以下のディレクトリに追加します。

ng-pain-log/
└── pain-log/
    └── src/
        └── app/
            ├── core/
            ├── domain/
            ├── features/
            │   └── components/
            │       └── search/
            │           ├── search.component.ts
            │           ├── search.component.html
            │           ├── search.component.less
            │           └── search.module.ts
            │   └── pages/
            │       └── dashboard/
            │           ├── dashboard.component.html
            │           ├── dashboard.component.ts
            │           └── dashboard.component.less
            ├── infrastructures/
            │   └── services/
            │       └── api.service.ts
            ├── shared/
            ├── app.component.html
            ├── app.component.ts
            ├── app.component.less
            └── app.routes.ts

以下はsearchコンポーネントの内容です。

search.component.html

<div class="search-container">
    <input class="input-name" #name placeholder="Name" />
    <input class="input-movement" #movement placeholder="Movement" />
    <button mat-flat-button color="basic" (click)="onSearch(name.value, movement.value)"
        class="search-button">Search</button>
    <button mat-stroked-button color="basic" (click)="onClear(name, movement)" class="clear-button">Clear</button>
</div>

search.component.ts

import { Component, ElementRef, EventEmitter, OnInit, Output, viewChild } from '@angular/core';
import { ApiService } from '../../../infrastructures/services/api.service'; 
import { PainLogResultDataEntity } from '../../../shared/models/pain-log-data-entity';
import { MatButton } from '@angular/material/button';

@Component({
  selector: 'app-search',
  imports: [MatButton],
  templateUrl: './search.component.html',
  styleUrl: './search.component.less'
})
export class SearchComponent implements OnInit {
  @Output() searchComplted = new EventEmitter<any[]>();
  painLog: PainLogResultDataEntity[] = []

  constructor(private apiService: ApiService, ) {}

  ngOnInit(): void { }

  onSearch(name: string, movement: string): void {
    this.apiService.searchPatients(name, movement).subscribe((data) => {
      this.painLog = data;
      this.searchComplted.emit(this.painLog);
    })
    
  }

  onClear(name: HTMLInputElement, movement: HTMLInputElement): void {
    name.value = '';
    movement.value = '';
  }
}

dashboardコンポーネントの内容も変更します。

dashboard.component.html

<app-search (searchComplted)="onSearchCompleted($event)"></app-search>

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Date Column -->
  <ng-container matColumnDef="date">
    <th mat-header-cell *matHeaderCellDef> Date </th>
    <td mat-cell *matCellDef="let element"> {{element.date}} </td>
  </ng-container>

  <!-- Name Column -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <!-- Movement Column -->
  <ng-container matColumnDef="movement">
    <th mat-header-cell *matHeaderCellDef> Movement </th>
    <td mat-cell *matCellDef="let element"> {{element.movement}} </td>
  </ng-container>

  <!-- VAS Column -->
  <ng-container matColumnDef="vas">
    <th mat-header-cell *matHeaderCellDef> VAS </th>
    <td mat-cell *matCellDef="let element"> {{element.vas}} </td>
  </ng-container>

  <!-- Memo Column -->
  <ng-container matColumnDef="memo">
    <th mat-header-cell *matHeaderCellDef> Memo </th>
    <td mat-cell *matCellDef="let element"> {{element.memo}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

dashboard.component.ts

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { PainLogResultDataEntity } from '../../../shared/models/pain-log-data-entity';
import { ApiService } from '../../../infrastructures/services/api.service';
import { SearchComponent } from '../../components/search/search.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, MatTableModule, SearchComponent],
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.less']
})

export class DashboardComponent {
  displayedColumns: string[] = ['date', 'name', 'movement', 'vas', 'memo'];
  dataSource: PainLogResultDataEntity[] = [];

  constructor(private service: ApiService) {
    this.service.selectPatients().subscribe((data: PainLogResultDataEntity[]) => {
      this.dataSource = data;
    });
  }

  onSearchCompleted(data: PainLogResultDataEntity[]): void {
    this.dataSource = data;
  }
}
npx ng serve --host 0.0.0.0 --poll 1

を実行すると、

デザインは変えているので、そこはいろいろ試してみてください!

メモリリークを意識した実装

メモリリークとは、コンピュータプログラムが不要になったメモリを解放しないことで、メモリの使用量が徐々に増えていく現象のことです。
これにより、システム全体のメモリが不足し、パフォーマンスが低下したり、最悪の場合にはプログラムが壊れたりすることがあります。

Angular では Observableの購読を解除しないとメモリリークの原因になります。その解決策としてasyncパイプを使用するのがおすすめです。

dashboard.component.html

<app-search (searchComplted)="onSearchCompleted($event)"></app-search>

@if (dataSource$ | async; as dataSource) {
  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

    <!-- Date Column -->
    <ng-container matColumnDef="date">
      <th mat-header-cell *matHeaderCellDef> Date </th>
      <td mat-cell *matCellDef="let element"> {{element.date}} </td>
    </ng-container>
  
    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>
  
    <!-- Movement Column -->
    <ng-container matColumnDef="movement">
      <th mat-header-cell *matHeaderCellDef> Movement </th>
      <td mat-cell *matCellDef="let element"> {{element.movement}} </td>
    </ng-container>
  
    <!-- VAS Column -->
    <ng-container matColumnDef="vas">
      <th mat-header-cell *matHeaderCellDef> VAS </th>
      <td mat-cell *matCellDef="let element"> {{element.vas}} </td>
    </ng-container>
  
    <!-- Memo Column -->
    <ng-container matColumnDef="memo">
      <th mat-header-cell *matHeaderCellDef> Memo </th>
      <td mat-cell *matCellDef="let element"> {{element.memo}} </td>
    </ng-container>
  
    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>  
}

dashboard.component.ts

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { PainLogResultDataEntity } from '../../../shared/models/pain-log-data-entity';
import { ApiService } from '../../../infrastructures/services/api.service';
import { SearchComponent } from '../../components/search/search.component';
import { Observable, of } from 'rxjs';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, MatTableModule, SearchComponent],
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.less']
})

export class DashboardComponent {
  displayedColumns: string[] = ['date', 'name', 'movement', 'vas', 'memo'];
  dataSource$!: Observable<PainLogResultDataEntity[]>;

  constructor(private service: ApiService) {
    this.dataSource$ = this.service.selectPatients();
  }

  onSearchCompleted(data: PainLogResultDataEntity[]): void {
    this.dataSource$ = of(data);
  }
}

この修正を行って、Angularを起動しても見た目は変わりませんが、中身は変わっています!

フロント&バックエンド起動

最後は全部起動してみます!

バックエンドは、下記サイトの「実行」を参考にして立ち上げてください。

フロントはいつも通り、

ng serve

または、

npx ng serve --host 0.0.0.0 --poll 1

を実行して、http://localhost:4200でウェブから見てみると、、、

連携できています!

検索も機能しているか確認してみてください!

次のステップ

追加、編集、削除を加えていく!

【Angular v19 + SpringBoot 最終回】編集、削除、追加
たけ坊今回でAngularとSpring Bootの大まかな実装が終わるよ最後まで頑張ります!前提ここまでの流れはこちらから!入力フォームの作成まずは、追加と編集で使用する入力フォームコンポーネント、input.componentを作成しま...
タイトルとURLをコピーしました