Skip to content
Open

Answer2 #1518

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
110 changes: 77 additions & 33 deletions apps/angular/crud/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,95 @@
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { randText } from '@ngneat/falso';
import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { BehaviorSubject, finalize, retry, tap } from 'rxjs';
import { Todo } from './todo';
import { TodoService } from './todo.service';

@Component({
standalone: true,
imports: [CommonModule],
imports: [CommonModule, MatProgressSpinnerModule],
selector: 'app-root',
template: `
<div *ngFor="let todo of todos">
{{ todo.title }}
<button (click)="update(todo)">Update</button>
</div>
@defer (when !loading()) {
@for (todo of todos(); track todo.id) {
<div>
{{ todo.title }} |
<button (click)="update(todo)">Update</button>
|
<button (click)="delete(todo.id)">Delete</button>
</div>
}
} @loading (minimum 1000) {
<mat-progress-spinner
mode="indeterminate"
value="50"></mat-progress-spinner>
} @error {
<p>There was a problem to load component.</p>
}
`,
styles: [],
styles: [
`
.mdc-circular-progress {
position: absolute;
transition: opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
margin: auto;
right: 0;
left: 0;
bottom: 0;
top: 0;
}
`,
],
})
export class AppComponent implements OnInit {
todos!: any[];
private todoService = inject(TodoService);
private readonly todosSubject$ = new BehaviorSubject<Todo[]>([]);
readonly todos = signal<Todo[]>([]);
loading = signal(true);
private subject$ = inject(DestroyRef);

constructor(private http: HttpClient) {}
endLoader(): void {
this.loading.set(false);
}

ngOnInit(): void {
this.http
.get<any[]>('https://jsonplaceholder.typicode.com/todos')
.subscribe((todos) => {
this.todos = todos;
});
this.todoService
.getAllTodos()
.pipe(
takeUntilDestroyed(this.subject$),
retry(2),
finalize(() => this.endLoader()),
tap((todos) => this.todos.set(todos)),
)
.subscribe();
}

update(todo: Todo) {
this.todoService
.updateSingleTodo(todo)
.pipe(
takeUntilDestroyed(this.subject$),
tap((updatedTodo) => {
this.todos.update((current) =>
current.map((t) => (t.id === updatedTodo.id ? updatedTodo : t)),
);
}),
)
.subscribe();
}

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,
delete(id: number) {
this.todoService
.removeSingleTodo(id)
.pipe(
takeUntilDestroyed(this.subject$),
tap(() => {
const current = this.todosSubject$.value;
this.todosSubject$.next(current.filter((t) => t.id !== id));
this.todos.update((current) => current.filter((t) => t.id !== id));
}),
{
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
},
)
.subscribe((todoUpdated: any) => {
this.todos[todoUpdated.id - 1] = todoUpdated;
});
.subscribe();
}
}
51 changes: 51 additions & 0 deletions apps/angular/crud/src/app/todo.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { randText } from '@ngneat/falso';
import { Observable, catchError, throwError } from 'rxjs';
import { Todo } from './todo';

@Injectable({
providedIn: 'root',
})
export class TodoService {
constructor(private http: HttpClient) {}

getAllTodos(): Observable<Todo[]> {
return this.http
.get<Todo[]>('https://jsonplaceholder.typicode.com/todos')
.pipe(catchError((err) => this.throwErrorMessage(err)));
}

updateSingleTodo(todo: Todo): Observable<Todo> {
return this.http
.put<Todo>(
`https://jsonplaceholder.typicode.com/todos/${todo.id}`,
{
id: todo.id,
title: randText(),
userId: todo.userId,
completed: todo.completed,
},
{
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
},
)
.pipe(catchError((err) => this.throwErrorMessage(err)));
}

removeSingleTodo(id: number): Observable<null> {
return this.http
.delete<null>(`https://jsonplaceholder.typicode.com/todos/${id}`, {
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
.pipe(catchError((err) => this.throwErrorMessage(err)));
}

throwErrorMessage(err: HttpErrorResponse): Observable<never> {
return throwError(() => new Error(err?.message || 'Server error'));
}
}
6 changes: 6 additions & 0 deletions apps/angular/crud/src/app/todo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { CityStore } from '../../data-access/city.store';
import { FakeHttpService } from '../../data-access/fake-http.service';
import { CardType } from '../../model/card.model';
import { City } from '../../model/city.model';
import { CardComponent } from '../../ui/card/card.component';

@Component({
selector: 'app-city-card',
template: 'TODO City',
template: `
<app-card
[list]="cities"
[type]="cardType"
customClass="bg-light-red"></app-card>
`,
standalone: true,
imports: [],
imports: [CardComponent],
})
export class CityCardComponent implements OnInit {
constructor() {}
cities: City[] = [];
cardType = CardType.CITY;

ngOnInit(): void {}
constructor(
private http: FakeHttpService,
private store: CityStore,
) {}

ngOnInit(): void {
this.http.fetchCities$.subscribe((s) => this.store.addAll(s));

this.store.cities$.subscribe((s) => (this.cities = s));
}
}
17 changes: 15 additions & 2 deletions apps/angular/projection/src/app/ui/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { NgFor, NgIf } from '@angular/common';
import { Component, Input } from '@angular/core';
import { randStudent, randTeacher } from '../../data-access/fake-http.service';
import { CityStore } from '../../data-access/city.store';
import {
randStudent,
randTeacher,
randomCity,
} from '../../data-access/fake-http.service';
import { StudentStore } from '../../data-access/student.store';
import { TeacherStore } from '../../data-access/teacher.store';
import { CardType } from '../../model/card.model';
Expand All @@ -21,10 +26,15 @@ import { ListItemComponent } from '../list-item/list-item.component';
src="assets/img/student.webp"
width="200px" />

<img
*ngIf="type === CardType.CITY"
src="assets/img/city.png"
width="200px" />

<section>
<app-list-item
*ngFor="let item of list"
[name]="item.firstName"
[name]="item.firstName ?? item.name"
[id]="item.id"
[type]="type"></app-list-item>
</section>
Expand All @@ -49,13 +59,16 @@ export class CardComponent {
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
private cityStore: CityStore,
) {}

addNewItem() {
if (this.type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (this.type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
} else if (this.type === CardType.CITY) {
this.cityStore.addOne(randomCity());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, Input } from '@angular/core';
import { CityStore } from '../../data-access/city.store';
import { StudentStore } from '../../data-access/student.store';
import { TeacherStore } from '../../data-access/teacher.store';
import { CardType } from '../../model/card.model';
Expand All @@ -23,13 +24,16 @@ export class ListItemComponent {
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
private cityStore: CityStore,
) {}

delete(id: number) {
if (this.type === CardType.TEACHER) {
this.teacherStore.deleteOne(id);
} else if (this.type === CardType.STUDENT) {
this.studentStore.deleteOne(id);
} else if (this.type === CardType.CITY) {
this.cityStore.deleteOne(id);
}
}
}
Loading