Last active
February 19, 2025 22:08
-
-
Save ifukazoo/5b5b42f18cbcf6985bfcff0a89dc3b8e to your computer and use it in GitHub Desktop.
Angular試行錯誤
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
}) | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', // 進捗イベントを取得可能 | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:host { | |
display: flex; | |
flex-direction: column; | |
align-items: flex-start; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="upload-container"> | |
<input type="file" (change)="onFileSelected($event)" /> | |
<button (click)="onUpload()" [disabled]="!selectedFile">アップロード</button> | |
<div *ngIf="uploadProgress > 0"> | |
アップロード進行中: {{ uploadProgress }}% | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}, | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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