Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions apps/angular/5-crud-application/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -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;
}
69 changes: 35 additions & 34 deletions apps/angular/5-crud-application/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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 }}
<button (click)="update(todo)">Update</button>
}
<div class="table-design"></div>
<table border="1">
<thead>
<tr>
<th>Title</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
@for (todo of todos(); track todo.id) {
<tr>
<td>{{ todo.title }}</td>
<td>
<button (click)="update(todo)">Update</button>
</td>
<td>
<button (click)="delete(todo)">Delete</button>
</td>
</tr>
}
</tbody>
</table>
`,
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<any[]>('https://jsonplaceholder.typicode.com/todos')
.subscribe((todos) => {
this.todos = todos;
});
this.fakehttpService.getAllTodos();
}

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;
});
update(todo: TODO) {
this.fakehttpService.updateTodo(todo);
}
delete(todo: TODO) {
this.fakehttpService.deleteTodo(todo);
}
}
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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<TODO[]>([]);

getAllTodos() {
this.http
.get<TODO[]>('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<TODO>(
`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<TODO>(`https://jsonplaceholder.typicode.com/todos/${todo.id}`)
.subscribe(() =>
this.todoSignal.update((todos) =>
todos.filter((t) => t.id !== todo.id),
),
);
}
}
5 changes: 5 additions & 0 deletions apps/angular/5-crud-application/src/app/model/todo.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface TODO {
id: number;
title: string;
completed: boolean;
}
3 changes: 3 additions & 0 deletions apps/angular/5-crud-application/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
Loading