diff --git a/apps/angular/5-crud-application/src/app/app.component.scss b/apps/angular/5-crud-application/src/app/app.component.scss
new file mode 100644
index 000000000..391039033
--- /dev/null
+++ b/apps/angular/5-crud-application/src/app/app.component.scss
@@ -0,0 +1,49 @@
+.table-design {
+ margin: 20px;
+
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 20px 0;
+
+}
+
+th, td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+}
+
+th {
+ background-color: #f2f2f2;
+ font-weight: bold;
+}
+
+tr:hover {
+ background-color: #f5f5f5;
+}
+
+button {
+ padding: 8px 16px;
+ margin: 0 4px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+button:first-of-type {
+ background-color: #4CAF50;
+ color: white;
+}
+
+button:last-of-type {
+ background-color: #f44336;
+ color: white;
+}
+
+button:hover {
+ opacity: 0.8;
+}
\ No newline at end of file
diff --git a/apps/angular/5-crud-application/src/app/app.component.ts b/apps/angular/5-crud-application/src/app/app.component.ts
index 73ba0dc34..d33b22606 100644
--- a/apps/angular/5-crud-application/src/app/app.component.ts
+++ b/apps/angular/5-crud-application/src/app/app.component.ts
@@ -1,49 +1,50 @@
-import { HttpClient } from '@angular/common/http';
import { Component, inject, OnInit } from '@angular/core';
-import { randText } from '@ngneat/falso';
+
+import { FakeHttpService } from './data-access/fake-http.service';
+import { TODO } from './model/todo.model';
@Component({
imports: [],
selector: 'app-root',
template: `
- @for (todo of todos; track todo.id) {
- {{ todo.title }}
-
- }
+
+
+
+
+ | Title |
+ Update |
+ Delete |
+
+
+
+ @for (todo of todos(); track todo.id) {
+
+ | {{ todo.title }} |
+
+
+ |
+
+
+ |
+
+ }
+
+
`,
- styles: [],
+ styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
- private http = inject(HttpClient);
-
- todos!: any[];
+ fakehttpService = inject(FakeHttpService);
+ readonly todos = this.fakehttpService.todoSignal;
ngOnInit(): void {
- this.http
- .get('https://jsonplaceholder.typicode.com/todos')
- .subscribe((todos) => {
- this.todos = todos;
- });
+ this.fakehttpService.getAllTodos();
}
- update(todo: any) {
- this.http
- .put(
- `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;
- });
+ update(todo: TODO) {
+ this.fakehttpService.updateTodo(todo);
+ }
+ delete(todo: TODO) {
+ this.fakehttpService.deleteTodo(todo);
}
}
diff --git a/apps/angular/5-crud-application/src/app/data-access/fake-http.service.spec.ts b/apps/angular/5-crud-application/src/app/data-access/fake-http.service.spec.ts
new file mode 100644
index 000000000..e04494ec1
--- /dev/null
+++ b/apps/angular/5-crud-application/src/app/data-access/fake-http.service.spec.ts
@@ -0,0 +1,95 @@
+import {
+ HttpClientTestingModule,
+ HttpTestingController,
+} from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { FakeHttpService } from './fake-http.service';
+
+describe('FakeHTTPService', () => {
+ let service: FakeHttpService;
+ let httpMock: HttpTestingController;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule],
+ });
+ service = TestBed.inject(FakeHttpService);
+ httpMock = TestBed.inject(HttpTestingController);
+ });
+
+ test('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+ test('should fetch all todos and update signals', () => {
+ // Arrange
+ const mockTodos = [
+ {
+ id: 1,
+ title: 'Test todo',
+ body: 'Todo body',
+ completed: 'false',
+ },
+ ];
+
+ // Act
+ service.getAllTodos();
+
+ // Assert request
+ const req = httpMock.expectOne(
+ 'https://jsonplaceholder.typicode.com/todos',
+ );
+ expect(req.request.method).toBe('GET');
+ req.flush(mockTodos);
+
+ // Assert signal update
+ expect(service.todoSignal()).toEqual(mockTodos);
+ });
+
+ test('should pass updated todo and update signal', () => {
+ // Arrange
+ const initialTodo = [
+ {
+ id: 1,
+ title: 'old',
+ completed: false,
+ },
+ ];
+ service.todoSignal.set(initialTodo);
+
+ const updatedTodo = {
+ id: 1,
+ title: 'new',
+ completed: true,
+ };
+
+ service.updateTodo(updatedTodo);
+
+ const req = httpMock.expectOne(
+ 'https://jsonplaceholder.typicode.com/todos/1',
+ );
+ expect(req.request.method).toBe('PUT');
+ req.flush(updatedTodo);
+
+ expect(service.todoSignal()[0]).toEqual(updatedTodo);
+ });
+
+ test('should delete the delted todo and update the signal', () => {
+ const initalTodo = [
+ {
+ id: 1,
+ title: 'old',
+ completed: false,
+ },
+ ];
+ service.todoSignal.set(initalTodo);
+ service.deleteTodo(initalTodo[0]);
+
+ const req = httpMock.expectOne(
+ 'https://jsonplaceholder.typicode.com/todos/1',
+ );
+ expect(req.request.method).toBe('DELETE');
+ req.flush({});
+ expect(service.todoSignal().length).toBe(0);
+ });
+});
diff --git a/apps/angular/5-crud-application/src/app/data-access/fake-http.service.ts b/apps/angular/5-crud-application/src/app/data-access/fake-http.service.ts
new file mode 100644
index 000000000..0b744fffc
--- /dev/null
+++ b/apps/angular/5-crud-application/src/app/data-access/fake-http.service.ts
@@ -0,0 +1,60 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable, signal } from '@angular/core';
+import { randText } from '@ngneat/falso';
+import { TODO } from '../model/todo.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class FakeHttpService {
+ private http = inject(HttpClient);
+ todoSignal = signal([]);
+
+ getAllTodos() {
+ this.http
+ .get('https://jsonplaceholder.typicode.com/todos')
+ .subscribe({
+ next: (todosResponse: TODO[]) => {
+ this.todoSignal.set(todosResponse);
+ },
+ error: (err) => {
+ console.error('Failed to load todos:', err);
+ },
+ });
+ }
+
+ updateTodo(todo: TODO) {
+ this.http
+ .put(
+ `https://jsonplaceholder.typicode.com/todos/${todo.id}`,
+ {
+ ...todo,
+ title: randText(),
+ },
+ {
+ headers: {
+ 'Content-type': 'application/json; charset=UTF-8',
+ },
+ },
+ )
+ .subscribe({
+ next: (updated) => {
+ this.todoSignal.update((todos) =>
+ todos.map((t) => (t.id === updated.id ? updated : t)),
+ );
+ },
+ error: (err) => {
+ console.error('Failed to load todos:', err);
+ },
+ });
+ }
+ deleteTodo(todo: TODO) {
+ this.http
+ .delete(`https://jsonplaceholder.typicode.com/todos/${todo.id}`)
+ .subscribe(() =>
+ this.todoSignal.update((todos) =>
+ todos.filter((t) => t.id !== todo.id),
+ ),
+ );
+ }
+}
diff --git a/apps/angular/5-crud-application/src/app/model/todo.model.ts b/apps/angular/5-crud-application/src/app/model/todo.model.ts
new file mode 100644
index 000000000..6092bb065
--- /dev/null
+++ b/apps/angular/5-crud-application/src/app/model/todo.model.ts
@@ -0,0 +1,5 @@
+export interface TODO {
+ id: number;
+ title: string;
+ completed: boolean;
+}
diff --git a/apps/angular/5-crud-application/tsconfig.json b/apps/angular/5-crud-application/tsconfig.json
index a7033d03a..6c490f80c 100644
--- a/apps/angular/5-crud-application/tsconfig.json
+++ b/apps/angular/5-crud-application/tsconfig.json
@@ -5,6 +5,9 @@
"references": [
{
"path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 53c7f6057..56390c7a0 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -14,6 +14,7 @@
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
+ "ignoreDeprecations": "6.0",
"paths": {
"@angular-challenges/cli": ["libs/cli/src/index.ts"],
"@angular-challenges/custom-plugin": ["libs/custom-plugin/src/index.ts"],