Skip to content

Instantly share code, notes, and snippets.

@ifukazoo
Last active February 19, 2025 22:08
Show Gist options
  • Save ifukazoo/5b5b42f18cbcf6985bfcff0a89dc3b8e to your computer and use it in GitHub Desktop.
Save ifukazoo/5b5b42f18cbcf6985bfcff0a89dc3b8e to your computer and use it in GitHub Desktop.
Angular試行錯誤
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private readonly endpoint = 'your-endpoint';
constructor(private http: HttpClient) {}
// エラーハンドリングを含めた Observable を返すメソッド
getDataWithHandling(): Observable<any> {
return this.http.get<any>(this.endpoint, { observe: 'response' })
.pipe(
tap((res: HttpResponse<any>) => {
if (res.status !== 200) {
// 正常系以外のケースは、必要に応じてここで処理
console.warn('Non-200 status received in tap:', res.status);
}
}),
catchError((error: HttpErrorResponse) => {
if (error.status === 0) {
console.error('Network error:', error.message);
} else if (error.status === 500) {
console.error('Server error (500):', error.message);
} else {
console.error(`Error (${error.status}):`, error.message);
}
// エラーを再スローするか、フォールバック値を返すか選択
return throwError(() => error);
})
);
}
}
<mat-toolbar class="header">
<img class="header-logo-img" src="images/image-logo.svg" alt="logo">
<span class="header-logo-title" mat-margin-left="32px">Designer/Developer</span>
<span class="spacer"></span>
<button mat-flat-button>Home</button>
<button mat-button color="accent" routerLink="/home">Home</button>
<button mat-button routerLink="/about">About</button>
<button mat-button routerLink="/contact">Contact</button>
<button mat-button routerLink="/login">Login</button>
<button mat-button routerLink="/register">Register</button>
<button mat-button routerLink="/profile">Profile</button>
<button mat-button routerLink="/logout">Logout</button>
</mat-toolbar>
<div class="container">
<button mat-fab extended>Logout</button>
<button mat-fab extended>Logout</button>
</div>
<div style="width: 2400px; overflow-x: auto; white-space: nowrap;">
<mat-grid-list cols="9" rowHeight="300px">
<mat-grid-tile>1</mat-grid-tile>
@for (d of formData; track d; let i = $index) {
<mat-grid-tile><app-formsample-a [index]="i" [inputForm]="d"
(formDataChange)="onFormDataChange($event)"></app-formsample-a></mat-grid-tile>
}
</mat-grid-list>
</div>
<p>
この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。
</p>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<table>
<thead>
<tr>
<th>Name</th>
<th>Quantity</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody formArrayName="items">
<tr *ngFor="let item of items.controls; let i = index" [formGroupName]="i">
<td><input formControlName="name" placeholder="Name" /></td>
<td><input type="number" formControlName="quantity" /></td>
<td><input type="number" formControlName="price" /></td>
<td><button type="button" (click)="removeItem(i)">Remove</button></td>
</tr>
</tbody>
</table>
<button type="button" (click)="addItem()">Add Row</button>
<button type="submit" [disabled]="form.invalid">Submit</button>
</form>
<router-outlet />
.header-logo-title {
font-size: 1.5rem;
margin-left: 32px;
}
.header {
background: #6b4e71;
color: white;
}
.spacer {
flex: 1 1 auto;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
button {
margin-top: 10px;
}
}
.mat-grid-tile {
border-style: double;
min-width: max-content;
}
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatCardModule } from '@angular/material/card';
import { FormsampleAComponent } from './components/formsample-a/formsample-a.component';
import { FormArray, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common'; // CommonModuleをインポート
export type InputData = {
inputField: string;
selectField: 'one' | 'two' | undefined;
textareaField: string;
};
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, MatToolbarModule, MatButtonModule, MatGridListModule, MatCardModule, FormsampleAComponent, ReactiveFormsModule, CommonModule],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'blockcoding';
formData: InputData[] = [
{
inputField: "Sample input 1",
selectField: "one",
textareaField: "This is the first sample textarea",
},
{
inputField: "Sample input 2",
selectField: "two",
textareaField: "This is the second sample textarea",
},
{
inputField: "Sample input 3",
selectField: undefined,
textareaField: "This is the third sample textarea",
},
{
inputField: "Sample input 4",
selectField: "one",
textareaField: "This is the fourth sample textarea",
},
{
inputField: "Sample input 5",
selectField: "two",
textareaField: "This is the fifth sample textarea",
},
{
inputField: "Sample input 6",
selectField: undefined,
textareaField: "This is the sixth sample textarea",
},
{
inputField: "Sample input 7",
selectField: "one",
textareaField: "This is the seventh sample textarea",
},
{
inputField: "Sample input 8",
selectField: "two",
textareaField: "This is the eighth sample textarea",
},
];
formDataM: InputData[] = [...this.formData]
// @Output() formDataChange = new EventEmitter<{ id: number, data: InputData }>();
onFormDataChange(event: { id: number, data: InputData }) {
console.log('event:', event)
this.formDataM[event.id] = event.data;
console.log('Form data updated:', this.formDataM);
}
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
items: this.fb.array([]), // FormArrayの初期化
});
}
get items(): FormArray {
return this.form.get('items') as FormArray;
}
addItem(): void {
console.log('addItem');
this.items.push(this.fb.group({
name: ['', Validators.required],
quantity: [1, [Validators.required, Validators.min(1)]],
price: [0, [Validators.required, Validators.min(0)]],
}));
}
removeItem(index: number): void {
this.items.removeAt(index);
}
onSubmit(): void {
console.log(this.form.value);
}
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class FileUploadService {
constructor(private http: HttpClient) {}
uploadFile(uploadUrl: string, file: File): Observable<any> {
const formData = new FormData();
formData.append('file_movie', file);
return this.http.post(uploadUrl, formData, {
headers: new HttpHeaders({
// 'Content-Type': 'multipart/form-data' は **不要**(自動で設定される)
}),
reportProgress: true,
observe: 'events', // 進捗イベントを取得可能
});
}
}
<div style="display: flex; flex-direction: column;" [formGroup]="myForm">
<mat-form-field>
<mat-label>Input</mat-label>
<input matInput formControlName="inputField">
</mat-form-field>
<mat-form-field>
<mat-label>Select</mat-label>
<mat-select formControlName="selectField">
<mat-option value="one">First option</mat-option>
<mat-option value="two">Second option</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Textarea</mat-label>
<textarea matInput formControlName="textareaField"></textarea>
</mat-form-field>
</div>
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
}
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { InputData } from '../../app.component';
@Component({
selector: 'app-formsample-a',
standalone: true,
imports: [MatFormFieldModule, MatInputModule, MatSelectModule, ReactiveFormsModule],
templateUrl: './formsample-a.component.html',
styleUrl: './formsample-a.component.scss'
})
export class FormsampleAComponent implements OnInit {
@Input() index: number = 0;
@Input() inputForm: InputData = {
inputField: '',
selectField: undefined,
textareaField: ''
};
@Output() formDataChange = new EventEmitter<{ id: number, data: InputData }>();
myForm: FormGroup;
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
inputField: [this.inputForm.inputField, [Validators.required, Validators.minLength(3)]],
selectField: [this.inputForm.selectField, Validators.required],
textareaField: [this.inputForm.textareaField, [Validators.required, Validators.maxLength(500)]],
});
}
ngOnInit(): void {
// 初期値をフォームに設定
this.myForm.patchValue(this.inputForm);
// フォームの変更を監視して通知
this.myForm.valueChanges
.pipe(distinctUntilChanged())
.subscribe(value => {
console.log('Form valid:', value);
if (this.myForm.valid) {
this.formDataChange.emit({ id: this.index, data: value });
}
});
}
}
<form [formGroup]="form">
<div formArrayName="users">
<div *ngFor="let user of users.controls; let i = index" [formGroupName]="i">
<input formControlName="name" />
{{user.value.name}}
{{user.value.email}}
<input formControlName="email" />
<select formControlName="role">
<option *ngFor="let role of roletable | keyvalue; let i = index" [value]="role.key">
{{ role.value }}
</option>
</select>
<button type="button" (click)="removeUser(i)">削除</button>
</div>
</div>
<div>
<input formControlName="title" />
</div>
<button type="button" (click)="onSubmit2()">送信</button>
</form>
import { Component, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
// Viewの型
type UserView = {
name: string;
email: string;
role_id: number;
}
type TrainView = {
name: string;
}
type TrainForm = FormControls<TrainView>;
// Forの型
type FormControls<T> = {
[K in keyof T]: FormControl<T[K]>;
};
type UserForm = FormGroup<FormControls<UserView>>;
@Component({
selector: 'app-formsample-b',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
],
templateUrl: './formsample-b.component.html',
styleUrl: './formsample-b.component.scss'
})
export class FormsampleBComponent {
// 全体
form: FormGroup<{
users: FormArray<UserForm>,
title: FormControl<string>,
}>;
constructor(private fb: FormBuilder) {
// 1)FormGroupをFormArrayとして利用
this.form = this.fb.group({
users: this.fb.array<UserForm>([]),
title: this.fb.nonNullable.control<string>('', [Validators.required, Validators.minLength(5)]), // 追加
});
}
// 2)FormArrayのGetter/Property。テンプレート側でletで回す。
get users(): FormArray<UserForm> {
return this.form.get('users') as FormArray<UserForm>;
}
get title(): FormControl<string> {
return this.form.get('title') as FormControl<string>;
}
// 3)FormGroupの型定義が暗黙にここで作られる
private createUser(user?: UserView): FormGroup {
return this.fb.group({
name: [user?.name || '', Validators.required,],
email: [user?.email || '', [Validators.required, Validators.email]],
role: [user?.role_id || 1, Validators.required]
});
}
// 4)FormArrayにデータをセット
setUsers(usersData: UserView[]) {
this.users.clear(); // 一度クリアする
usersData.forEach(user => {
this.users.push(this.createUser(user));
});
this.title.setValue(usersData.at(0)?.name || 'hoge');
}
roletable: Map<number, string> = new Map([])
// サーバーからデータを取得
fetchUsers() {
setTimeout(() => {
const sampleData: UserEntity[] = [
{ name: 'Alice', email: '[email protected]', role_id: 1, role: 'admin' },
{ name: 'Bob', email: '[email protected]', role_id: 2, role: '編集者' },
{ name: 'Charlie', email: '[email protected]', role_id: 3, role: '閲覧者' }
];
sampleData.forEach(element => {
this.roletable.set(element.role_id, element.role);
});
// 部分型なので直接代入できる
const c: UserView[] = sampleData;
this.setUsers(c)
console.log(sampleData);
}, 1000); // 1秒後にデータ取得
}
// フォームを手動で追加
addUser(): void {
this.users.push(this.createUser());
}
// 指定したインデックスのフォームを削除
removeUser(index: number): void {
this.users.removeAt(index);
}
onSubmit2(): void {
for (const control of this.users.controls) {
console.log(control.get('name')?.hasError('required'));
}
if (this.form.valid) {
console.log('フォーム送信', this.form.value);
} else {
console.log('フォームが無効です');
}
}
// フォームの送信処理
onSubmit(): void {
for (const control of this.users.controls) {
console.log(control.get('name')?.hasError('required'));
}
if (this.form.valid) {
console.log('フォーム送信', this.form.value.users);
} else {
console.log('フォームが無効です');
}
}
// 初回ロード時にデータ取得
ngOnInit(): void {
this.fetchUsers();
}
}
// DBから取得するデータの型定義
type UserEntity = {
name: string;
email: string;
role_id: number;
role: string;
}
<div>{{view.name}}</div>
<div>{{view.email}}</div>
<div>{{view.camera}}</div>
<div>{{view.fps}}</div>
<form [formGroup]="form">
<input formControlName="name" />
<input formControlName="email" />
<select formControlName="camera">
<option *ngFor="let camera of cameraEntries" [value]="camera[1]">
{{ camera[1] }}
</option>
</select>
<select formControlName="fps">
<option *ngFor="let fps of fspEntries" [value]="fps[1]">
{{ fps[1] }}
</option>
</select>
<button type="button" (click)="onSubmit()">送信</button>
</form>
<div class="upload-container">
<input type="file" (change)="onFileSelected($event)" />
<button (click)="onUpload()" [disabled]="!selectedFile">アップロード</button>
<div *ngIf="uploadProgress > 0">
アップロード進行中: {{ uploadProgress }}%
</div>
</div>
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { FileUploadService } from '../../services/file-upload.service';
const UPLOAD_URL = 'http://localhost:4000/api/upload';
@Component({
selector: 'app-formsample-d',
standalone: true,
imports: [CommonModule],
templateUrl: './formsample-d.component.html',
styleUrl: './formsample-d.component.scss',
})
export class FormsampleDComponent {
selectedFile: File | null = null;
uploadProgress: number = 0;
constructor(private fileUploadService: FileUploadService) {}
onFileSelected(event: Event): void {
const element = event.target as HTMLInputElement;
const file = element.files && element.files[0];
if (file) {
this.selectedFile = file;
}
}
onUpload(): void {
if (!this.selectedFile) {
alert('ファイルを選択してください');
return;
}
this.fileUploadService.uploadFile(UPLOAD_URL, this.selectedFile).subscribe({
next: (event: HttpEvent<unknown>) => {
switch (event.type) {
case HttpEventType.UploadProgress:
if (event.total) {
this.uploadProgress = Math.round(
(event.loaded / event.total) * 100
);
}
break;
case HttpEventType.Response:
alert('ファイルがアップロードされました');
this.uploadProgress = 0;
break;
}
},
error: (err) => {
console.error('アップロードエラー:', err);
alert('アップロードに失敗しました');
this.uploadProgress = 0;
},
});
}
}
// your.component.ts
import { Component } from '@angular/core';
import { ApiService } from './api.service';
@Component({
selector: 'app-your',
template: `<button (click)="fetchData()">Fetch Data</button>`
})
export class YourComponent {
constructor(private apiService: ApiService) {}
fetchData(): void {
this.apiService.getDataWithHandling().subscribe({
next: (res) => {
// ここでは tap 内での処理が済んでいる前提
console.log('Processed data:', res.body);
},
error: (error) => {
// 共通のエラー処理が既にサービス側で行われているので、ここで追加対応も可能
console.error('Handled in component:', error);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment