Skip to content

Commit 1a513c7

Browse files
error handling
1 parent b0b3540 commit 1a513c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+848
-544
lines changed

angular.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"polyfills": [
2020
"zone.js"
2121
],
22+
"allowedCommonJsDependencies": [
23+
"dayjs"
24+
],
2225
"tsConfig": "tsconfig.app.json",
2326
"assets": [
2427
"src/favicon.ico",

src/app/app.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import {Component} from '@angular/core';
22
import {RouterOutlet} from '@angular/router';
33
import {AsyncPipe} from "@angular/common";
4-
import {HttpClientModule} from "@angular/common/http";
54
import {LayoutComponent} from "@components/common/layout/layout.component";
65

76
@Component({
87
selector: 'app-root',
98
standalone: true,
10-
imports: [RouterOutlet, AsyncPipe, HttpClientModule, LayoutComponent],
9+
imports: [RouterOutlet, AsyncPipe, LayoutComponent],
1110
templateUrl: './app.component.html',
1211
styleUrl: './app.component.css'
1312
})

src/app/app.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import {ApplicationConfig} from '@angular/core';
1+
import {ApplicationConfig,provideZoneChangeDetection} from '@angular/core';
22
import {provideRouter} from '@angular/router';
33
import {routes} from './app.routes';
44
import {provideClientHydration} from '@angular/platform-browser';
55
import {provideHttpClient, withFetch} from "@angular/common/http";
66

77
export const appConfig: ApplicationConfig = {
88
providers: [
9+
provideZoneChangeDetection({ eventCoalescing: true }),
910
provideRouter(routes),
1011
provideHttpClient(withFetch()),
1112
provideClientHydration(),
12-
1313
]
1414
};
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<div class=" w-full px-4 mt-4">
22
<div class="flex items-center justify-between">
3-
<a [routerLink]="['/book']"
4-
class="text-blue-600 hover:text-blue-800">
5-
Back to list
6-
</a>
3+
<app-back-to-list url="/books" />
74
</div>
85
@if (isLoading()) {
9-
<div class="bg-blue-100 rounded py-4 px-4 text-blue-700 text-sm">Loading ...</div>
10-
} @else {
11-
<h1 class="text-3xl my-4">Create</h1>
12-
<app-form-book [item]="item()" (submit)="onSubmit($event)"/>
6+
<app-alert type="loading"/>
7+
}
8+
@if (error()) {
9+
<app-alert type="error" [error]="error"/>
1310
}
11+
<h1 class="text-3xl my-4">Create Book</h1>
12+
<app-form-book [item]="item()" (submit)="onSubmit($event)"/>
1413
</div>

src/app/components/book/create/create.component.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ import { Location } from "@angular/common";
22
import { Component, inject, signal, WritableSignal } from "@angular/core";
33
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
44
import { RouterLink } from "@angular/router";
5+
import { AlertComponent } from "@components/common/alert/alert.component";
6+
import { BackToListComponent } from "@components/common/back-to-list/back-to-list.component";
57
import { DeleteComponent } from "@components/common/delete/delete.component";
68
import { FormComponent } from "@components/book/form/form.component";
7-
import { ApiItem } from "@interface/api";
9+
import { ApiItem, SubmissionErrors } from "@interface/api";
810
import { ApiService } from "@service/api.service";
911

1012
@Component({
1113
selector: "app-create-book",
1214
standalone: true,
1315
imports: [
16+
AlertComponent,
17+
BackToListComponent,
1418
DeleteComponent,
1519
RouterLink,
1620
FormsModule,
@@ -24,11 +28,15 @@ export class CreateComponent {
2428
private location: Location = inject(Location);
2529
public item: WritableSignal<ApiItem> = signal({} as ApiItem);
2630
public isLoading: WritableSignal<boolean> = signal(false);
31+
public error: WritableSignal<SubmissionErrors | null> = signal(null);
2732

2833
onSubmit(data: any) {
29-
return this.apiService.add("/book", this.item()).subscribe((item) => {
30-
this.isLoading.set(true);
31-
this.location.back();
34+
return this.apiService.add("/books", this.item()).subscribe({
35+
next: () => {
36+
this.isLoading.set(true);
37+
this.location.back();
38+
},
39+
error: (err: SubmissionErrors) => this.error.set(err),
3240
});
3341
}
3442
}
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
<div class="px-4 mt-4">
22
<div class="flex items-center justify-end">
3-
<a [routerLink]="['/book']"
4-
class="text-blue-600 hover:text-blue-800">
5-
Back to list
6-
</a>
3+
<app-back-to-list url="/books"/>
74
</div>
85
@if (isLoading()) {
9-
<div class="bg-blue-100 rounded py-4 px-4 text-blue-700 text-sm">Loading ...</div>
10-
} @else if (error()) {
11-
<div class="bg-red-100 rounded py-4 px-4 text-blue-700 text-sm">Error ...</div>
12-
} @else {
13-
<h1 class="text-3xl my-4">Edit {{ item()['name'] }} <span class="text-xl">{{ item()["@id"] }} </span></h1>
14-
<app-form-book [item]="item()"
15-
(submit)="onSubmit($event)"
16-
(delete)="delete()"
17-
/>
6+
<app-alert type="loading"/>
7+
}
8+
@if (error()) {
9+
<app-alert type="error" [error]="error"/>
1810
}
11+
<h1 class="text-3xl my-4">
12+
Edit {{ item()['name'] }}
13+
<span class="text-xl">{{ item()["@id"] }}</span>
14+
</h1>
15+
<app-form-book
16+
[item]="item()"
17+
(submit)="onSubmit($event)"
18+
(delete)="delete()"
19+
/>
1920
</div>

src/app/components/book/edit/edit.component.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@ import {
1010
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
1111
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
1212
import { Router, RouterLink } from "@angular/router";
13+
import { AlertComponent } from "@components/common/alert/alert.component";
14+
import { BackToListComponent } from "@components/common/back-to-list/back-to-list.component";
1315
import { DeleteComponent } from "@components/common/delete/delete.component";
1416
import { FormComponent } from "@components/book/form/form.component";
15-
import { ApiItem } from "@interface/api";
17+
import { ApiItem, SubmissionErrors } from "@interface/api";
1618
import { ApiService } from "@service/api.service";
1719

1820
@Component({
1921
selector: "app-edit-book",
2022
standalone: true,
2123
imports: [
24+
AlertComponent,
25+
BackToListComponent,
2226
CommonModule,
2327
DeleteComponent,
2428
RouterLink,
@@ -31,34 +35,46 @@ import { ApiService } from "@service/api.service";
3135
export class EditComponent implements OnInit {
3236
public item: WritableSignal<ApiItem> = signal({} as ApiItem);
3337
public isLoading: WritableSignal<Boolean> = signal(false);
34-
public error: WritableSignal<string> = signal("");
35-
38+
public error: WritableSignal<SubmissionErrors | null> = signal(null);
3639
private destroy: DestroyRef = inject(DestroyRef);
3740
private apiService: ApiService = inject(ApiService);
3841
private router: Router = inject(Router);
3942
private location: Location = inject(Location);
4043

4144
ngOnInit() {
45+
this.fetchData();
46+
}
47+
48+
public fetchData() {
4249
const uri = this.router.url.split("/edit")[0];
43-
this.isLoading.set(true);
50+
this.toggleIsLoading();
4451
this.apiService
4552
.fetchData(uri)
4653
.pipe(takeUntilDestroyed(this.destroy))
47-
.subscribe((value) => {
48-
this.item.set(value);
49-
this.isLoading.set(false);
54+
.subscribe({
55+
next: (value) => {
56+
this.item.set(value);
57+
},
58+
error: (err) => this.error.set(err),
5059
});
60+
this.toggleIsLoading();
61+
}
62+
63+
public onSubmit(data: any) {
64+
return this.apiService.put(this.item()["@id"]!, this.item()).subscribe({
65+
next: () => this.location.back(),
66+
error: (err) => this.error.set(err),
67+
});
5168
}
5269

53-
onSubmit(data: any) {
54-
return this.apiService
55-
.put(this.item()["@id"]!, this.item())
56-
.subscribe(() => this.location.back());
70+
public delete() {
71+
return this.apiService.delete(this.item()["@id"]!).subscribe({
72+
next: () => this.location.back(),
73+
error: (err) => this.error.set(err),
74+
});
5775
}
5876

59-
delete() {
60-
return this.apiService
61-
.delete(this.item()["@id"]!)
62-
.subscribe(() => this.location.back());
77+
private toggleIsLoading() {
78+
return this.isLoading.update((value) => !value);
6379
}
6480
}

src/app/components/book/list/list.component.html

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@ <h1 class="text-3xl mb-2">Book List</h1>
1414
</div>
1515
</div>
1616
@if (isLoading()) {
17-
<div class="bg-blue-100 rounded py-4 px-4 text-blue-700 text-sm">Loading ...</div>
18-
} @else {
17+
<app-alert type="loading"/>
18+
}
19+
@if (error()) {
20+
<app-alert type="error" [error]="error"/>
21+
}
22+
1923
<app-table-book [items]="items"
2024
[bulk]="bulk()"
2125
(addToBulkList)="addToBulk($event)"
2226
(selectedAll)="selectedAll()"
2327
/>
24-
@if (pagination()) {
25-
<app-pagination
26-
[pagination]="pagination!"
27-
(handleChangePage)="changePage($event)"
28-
/>
29-
}
30-
}
28+
<app-pagination
29+
[pagination]="pagination"
30+
(handleChangePage)="changePage($event)"
31+
/>
32+
33+
<button (click)="navigateUrl('/bookmarks')">Test</button>
3134
</div>

src/app/components/book/list/list.component.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AsyncPipe, Location, NgFor, NgIf } from "@angular/common";
1+
import {NgFor, NgIf} from "@angular/common";
22
import {
33
Component,
44
DestroyRef,
@@ -7,13 +7,17 @@ import {
77
signal,
88
WritableSignal,
99
} from "@angular/core";
10-
import { Router, RouterLink } from "@angular/router";
11-
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
12-
import { DeleteComponent } from "@components/common/delete/delete.component";
13-
import { TableComponent } from "@components/book/table/table.component";
14-
import { ApiItem, Pagination } from "@interface/api";
15-
import { ApiService } from "@service/api.service";
16-
import { PaginationComponent } from "@components/common/pagination/pagination.component";
10+
import {NavigationEnd, Router, RouterLink, Scroll} from "@angular/router";
11+
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
12+
import {AlertComponent} from "@components/common/alert/alert.component";
13+
import {DeleteComponent} from "@components/common/delete/delete.component";
14+
import {TableComponent} from "@components/book/table/table.component";
15+
import {ApiItem, Pagination, SubmissionErrors} from "@interface/api";
16+
import {ApiService} from "@service/api.service";
17+
import {PaginationComponent} from "@components/common/pagination/pagination.component";
18+
import {filter} from "rxjs";
19+
import * as events from "node:events";
20+
import {UrlMatcherService} from "@service/url-matcher.service";
1721

1822
@Component({
1923
selector: "app-list-book",
@@ -25,41 +29,52 @@ import { PaginationComponent } from "@components/common/pagination/pagination.co
2529
NgIf,
2630
DeleteComponent,
2731
PaginationComponent,
32+
AlertComponent,
2833
],
2934
templateUrl: "./list.component.html",
3035
})
3136
export class ListComponent implements OnInit {
3237
public isLoading: WritableSignal<Boolean> = signal(false);
3338
public pagination: WritableSignal<Pagination> = signal({} as Pagination);
3439
public items: WritableSignal<ApiItem[]> = signal([]);
35-
public error: WritableSignal<String> = signal("");
40+
public error: WritableSignal<SubmissionErrors | null> = signal(null);
3641
public bulk: WritableSignal<Array<string>> = signal([]);
3742
public uri: WritableSignal<string> = signal("/books");
3843
private apiService: ApiService = inject(ApiService);
44+
private urlMatcherService = inject(UrlMatcherService)
3945
private destroy: DestroyRef = inject(DestroyRef);
46+
private router: Router = inject(Router)
4047

4148
ngOnInit() {
42-
this.fetchData();
49+
this.fetchData()
4350
}
4451

4552
public fetchData() {
4653
this.toggleIsLoading();
4754
this.apiService
4855
.fetchDataList(this.uri())
56+
// Unsubscribe event for more performance
4957
.pipe(takeUntilDestroyed(this.destroy))
50-
.subscribe((items) => {
51-
this.toggleIsLoading();
52-
if (items["hydra:view"]) this.pagination.set(items["hydra:view"]);
53-
this.items.set(items["hydra:member"]);
58+
.subscribe({
59+
next: (items) => {
60+
if (items["hydra:view"]) this.pagination.set(items["hydra:view"]);
61+
this.items.set(items["hydra:member"]);
62+
},
63+
error: (err: SubmissionErrors) => this.error.set(err),
5464
});
65+
this.toggleIsLoading();
66+
this.router.events.pipe(
67+
filter(event => event instanceof NavigationEnd)
68+
).subscribe(() => {
69+
window.scrollTo(0, 0);
70+
});
5571
}
5672

5773
public addToBulk(id: string) {
5874
if (this.isInBulkList(id)) {
5975
const bulkFilter = this.bulk().filter((element) => element !== id);
6076
return this.bulk.set(bulkFilter);
6177
}
62-
6378
this.bulk.update((uri) => [...uri, id]);
6479
}
6580

@@ -76,16 +91,26 @@ export class ListComponent implements OnInit {
7691
public delete() {
7792
Promise.all(this.bulk()).then((items) =>
7893
items.forEach((uri) =>
79-
this.apiService.delete(uri).subscribe(() => {
80-
window.location.reload();
94+
this.apiService.delete(uri).subscribe({
95+
next: () => {
96+
window.location.reload();
97+
},
98+
error: (err: SubmissionErrors) => this.error.set(err),
8199
})
82100
)
83101
);
84102
}
85103

104+
async navigateUrl(url: string) {
105+
await this.router
106+
.navigate([url])
107+
.then(res => res ? console.log('success', res) : console.log('error', res))
108+
}
109+
86110
public changePage(uri: string) {
87111
this.uri.set(uri);
88112
this.fetchData();
113+
89114
}
90115

91116
private toggleIsLoading() {

0 commit comments

Comments
 (0)