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

バージョンアップしてくんですね!
前提
これまでの流れはこちらから!
実装
検索機能を追加するために、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を作成しま...