Skip to content

Commit

Permalink
feat: angular-crud-application
Browse files Browse the repository at this point in the history
- add todo interface
- add todo service
- use signal for todo list
- add delete button
- handle errors globally
- add global loader
  • Loading branch information
Peace-Apple committed Sep 2, 2024
1 parent 9f34873 commit 875237c
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 35 deletions.
66 changes: 33 additions & 33 deletions apps/angular/5-crud-application/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { randText } from '@ngneat/falso';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { Todo } from './todo.interface';
import { TodoService } from './todo.service';

@Component({
standalone: true,
imports: [CommonModule],
imports: [CommonModule, MatSnackBarModule, MatProgressSpinnerModule],
selector: 'app-root',
template: `
<div *ngFor="let todo of todos">
{{ todo.title }}
<button (click)="update(todo)">Update</button>
</div>
@if (loadingSignal()) {
<mat-spinner></mat-spinner>
} @else {
<div *ngFor="let todo of todosSignal()">
{{ todo.title }}
<button (click)="updateTodo(todo)">Update</button>
<button (click)="deleteTodo(todo.id)">Delete</button>
</div>
}
`,
styles: [],
providers: [],
})
export class AppComponent implements OnInit {
todos!: any[];
todosSignal = this.todoService.todosSignal;
loadingSignal = this.todoService.loadingSignal;

constructor(private http: HttpClient) {}
constructor(private todoService: TodoService) {}

ngOnInit(): void {
this.http
.get<any[]>('https://jsonplaceholder.typicode.com/todos')
.subscribe((todos) => {
this.todos = todos;
});
this.todoService.getTodos();
}

update(todo: any) {
this.http
.put<any>(
`https://jsonplaceholder.typicode.com/todos/${todo.id}`,
JSON.stringify({
todo: todo.id,
title: randText(),
body: todo.body,
userId: todo.userId,
}),
{
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
},
)
.subscribe((todoUpdated: any) => {
this.todos[todoUpdated.id - 1] = todoUpdated;
});
updateTodo(todo: Todo) {
this.todoService.updateTodo(todo).subscribe((todoUpdated: Todo) => {
const updatedTodos = this.todosSignal().filter(
(todo) => todo.id !== todoUpdated.id,
);
this.todosSignal.set(updatedTodos);
});
}

deleteTodo(id: number) {
this.todoService.deleteTodo(id).subscribe(() => {
const allTodos = this.todosSignal().filter((t) => t.id !== id);
this.todosSignal.set(allTodos);
});
}
}
14 changes: 12 additions & 2 deletions apps/angular/5-crud-application/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { provideHttpClient } from '@angular/common/http';
import { HTTP_INTERCEPTORS, provideHttpClient } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { GlobalErrorInterceptor } from './error.interceptor';

export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
providers: [
provideHttpClient(),
provideAnimations(),
{
provide: HTTP_INTERCEPTORS,
useClass: GlobalErrorInterceptor,
multi: true,
},
],
};
59 changes: 59 additions & 0 deletions apps/angular/5-crud-application/src/app/error.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class GlobalErrorInterceptor implements HttpInterceptor {
constructor(private snackBar: MatSnackBar) {}

intercept(
request: HttpRequest<any>,
next: HttpHandler,
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
// Handle different types of errors here
let errorMessage = 'An unknown error occurred';

if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side error
switch (error.status) {
case 400:
errorMessage = 'Bad request';
break;
case 401:
errorMessage = 'Unauthorized, please log in';
break;
case 404:
errorMessage = 'Resource not found';
break;
case 500:
errorMessage = 'Internal server error';
break;
default:
errorMessage = `Error: ${error.message}`;
break;
}
}

// Show the error message using MatSnackBar
this.snackBar.open(errorMessage, 'Close', {
duration: 3000,
});

return throwError(() => new Error());
}),
);
}
}
7 changes: 7 additions & 0 deletions apps/angular/5-crud-application/src/app/todo.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Todo {
id: number;
title: string;
completed: boolean;
body: string;
userId: number;
}
16 changes: 16 additions & 0 deletions apps/angular/5-crud-application/src/app/todo.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { TodoService } from './todo.service';

describe('TodoService', () => {
let service: TodoService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TodoService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
51 changes: 51 additions & 0 deletions apps/angular/5-crud-application/src/app/todo.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HttpClient } from '@angular/common/http';
import { Injectable, signal } from '@angular/core';
import { randText } from '@ngneat/falso';
import { finalize, Observable } from 'rxjs';
import { Todo } from './todo.interface';

@Injectable({
providedIn: 'root',
})
export class TodoService {
private apiUrl = 'https://jsonplaceholder.typicode.com/todos';
todosSignal = signal<Todo[]>([]);
loadingSignal = signal<boolean>(false);

constructor(private http: HttpClient) {}

getTodos(): void {
this.loadingSignal.set(true);
this.http
.get<Todo[]>(this.apiUrl)
.pipe(finalize(() => this.loadingSignal.set(false)))
.subscribe((data) => this.todosSignal.set(data));
}

updateTodo(todo: Todo): Observable<Todo> {
this.loadingSignal.set(true);
return this.http
.put<Todo>(
`${this.apiUrl}/${todo.id}`,
JSON.stringify({
todo: todo.id,
title: randText(),
body: todo.body,
userId: todo.userId,
}),
{
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
},
)
.pipe(finalize(() => this.loadingSignal.set(false)));
}

deleteTodo(id: number): Observable<void> {
this.loadingSignal.set(true);
return this.http
.delete<void>(`${this.apiUrl}/${id}`)
.pipe(finalize(() => this.loadingSignal.set(false)));
}
}
Empty file.

0 comments on commit 875237c

Please sign in to comment.