Skip to content

Commit efbb0e0

Browse files
committed
feat: answer 1
1 parent dbf0e8a commit efbb0e0

File tree

7 files changed

+550
-815
lines changed

7 files changed

+550
-815
lines changed
Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,77 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
inject,
6+
OnInit,
7+
} from '@angular/core';
8+
import { CityStore } from '../../data-access/city.store';
9+
import {
10+
FakeHttpService,
11+
randomCity,
12+
} from '../../data-access/fake-http.service';
13+
import { CardComponent } from '../../ui/card/card.component';
214

315
@Component({
416
selector: 'app-city-card',
5-
template: 'TODO City',
6-
imports: [],
17+
template: `
18+
<app-card
19+
[image]="'assets/img/city.png'"
20+
[list]="vm()"
21+
customClass="city-card"
22+
(add)="add()"
23+
(delete)="delete($event)"></app-card>
24+
`,
25+
styles: [
26+
`
27+
.city-card {
28+
background-color: rgba(0, 0, 250, 0.1);
29+
}
30+
`,
31+
],
32+
imports: [CardComponent],
733
changeDetection: ChangeDetectionStrategy.OnPush,
834
})
9-
export class CityCardComponent {}
35+
export class CityCardComponent implements OnInit {
36+
private http = inject(FakeHttpService);
37+
private store = inject(CityStore);
38+
39+
vm = computed(() =>
40+
this.store.cities().map((c /*: City */) => ({
41+
id: c.id,
42+
label: c.name,
43+
})),
44+
);
45+
46+
ngOnInit(): void {
47+
this.http.fetchCities$.subscribe((c) => this.store.addAll(c));
48+
}
49+
50+
add() {
51+
this.store.addOne(randomCity());
52+
}
53+
54+
delete(id: number | Event) {
55+
let idNumber: number | undefined;
56+
57+
if (typeof id === 'number') {
58+
idNumber = id;
59+
} else {
60+
// If the emitter sends a CustomEvent with detail
61+
const maybeCustom = id as CustomEvent;
62+
if (maybeCustom?.detail !== undefined) {
63+
idNumber = Number(maybeCustom.detail);
64+
} else {
65+
// Fallback: try to read a value from the event target (e.g. input/button)
66+
const target = (id as Event).target as HTMLInputElement | null;
67+
if (target && target.value !== undefined) {
68+
idNumber = Number(target.value);
69+
}
70+
}
71+
}
72+
73+
if (typeof idNumber === 'number' && !Number.isNaN(idNumber)) {
74+
this.store.deleteOne(idNumber);
75+
}
76+
}
77+
}
Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import {
22
ChangeDetectionStrategy,
33
Component,
4+
computed,
45
inject,
56
OnInit,
67
} from '@angular/core';
7-
import { FakeHttpService } from '../../data-access/fake-http.service';
8+
import {
9+
FakeHttpService,
10+
randStudent,
11+
} from '../../data-access/fake-http.service';
812
import { StudentStore } from '../../data-access/student.store';
9-
import { CardType } from '../../model/card.model';
1013
import { CardComponent } from '../../ui/card/card.component';
1114

1215
@Component({
1316
selector: 'app-student-card',
1417
template: `
1518
<app-card
16-
[list]="students()"
17-
[type]="cardType"
18-
customClass="bg-light-green" />
19+
[image]="'assets/img/student.webp'"
20+
[list]="vm()"
21+
customClass="student-card"
22+
(add)="add()"
23+
(delete)="delete($event)"></app-card>
1924
`,
2025
styles: [
2126
`
22-
::ng-deep .bg-light-green {
27+
.student-card {
2328
background-color: rgba(0, 250, 0, 0.1);
2429
}
2530
`,
@@ -31,10 +36,42 @@ export class StudentCardComponent implements OnInit {
3136
private http = inject(FakeHttpService);
3237
private store = inject(StudentStore);
3338

34-
students = this.store.students;
35-
cardType = CardType.STUDENT;
39+
vm = computed(() =>
40+
this.store.students().map((s /*: Student */) => ({
41+
id: s.id,
42+
label: s.firstName,
43+
})),
44+
);
3645

3746
ngOnInit(): void {
3847
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
3948
}
49+
50+
add() {
51+
this.store.addOne(randStudent());
52+
}
53+
54+
delete(id: number | Event) {
55+
let idNumber: number | undefined;
56+
57+
if (typeof id === 'number') {
58+
idNumber = id;
59+
} else {
60+
// If the emitter sends a CustomEvent with detail
61+
const maybeCustom = id as CustomEvent;
62+
if (maybeCustom?.detail !== undefined) {
63+
idNumber = Number(maybeCustom.detail);
64+
} else {
65+
// Fallback: try to read a value from the event target (e.g. input/button)
66+
const target = (id as Event).target as HTMLInputElement | null;
67+
if (target && target.value !== undefined) {
68+
idNumber = Number(target.value);
69+
}
70+
}
71+
}
72+
73+
if (typeof idNumber === 'number' && !Number.isNaN(idNumber)) {
74+
this.store.deleteOne(idNumber);
75+
}
76+
}
4077
}
Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,77 @@
1-
import { Component, inject, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
inject,
6+
OnInit,
7+
} from '@angular/core';
8+
import {
9+
FakeHttpService,
10+
randTeacher,
11+
} from '../../data-access/fake-http.service';
312
import { TeacherStore } from '../../data-access/teacher.store';
4-
import { CardType } from '../../model/card.model';
513
import { CardComponent } from '../../ui/card/card.component';
614

715
@Component({
816
selector: 'app-teacher-card',
917
template: `
1018
<app-card
11-
[list]="teachers()"
12-
[type]="cardType"
13-
customClass="bg-light-red"></app-card>
19+
[image]="'assets/img/teacher.png'"
20+
[list]="vm()"
21+
customClass="teacher-card"
22+
(add)="add()"
23+
(delete)="delete($event)" />
1424
`,
1525
styles: [
1626
`
17-
::ng-deep .bg-light-red {
27+
.teacher-card {
1828
background-color: rgba(250, 0, 0, 0.1);
1929
}
2030
`,
2131
],
2232
imports: [CardComponent],
33+
changeDetection: ChangeDetectionStrategy.OnPush,
2334
})
2435
export class TeacherCardComponent implements OnInit {
2536
private http = inject(FakeHttpService);
2637
private store = inject(TeacherStore);
2738

28-
teachers = this.store.teachers;
29-
cardType = CardType.TEACHER;
39+
vm = computed(() =>
40+
this.store.teachers().map((t /*: Student */) => ({
41+
id: t.id,
42+
label: t.firstName,
43+
})),
44+
);
3045

3146
ngOnInit(): void {
3247
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
3348
}
49+
50+
add() {
51+
this.store.addOne(randTeacher()); // или store.addOne(randTeacher())
52+
}
53+
54+
delete(id: number | Event) {
55+
let idNumber: number | undefined;
56+
57+
if (typeof id === 'number') {
58+
idNumber = id;
59+
} else {
60+
// If the emitter sends a CustomEvent with detail
61+
const maybeCustom = id as CustomEvent;
62+
if (maybeCustom?.detail !== undefined) {
63+
idNumber = Number(maybeCustom.detail);
64+
} else {
65+
// Fallback: try to read a value from the event target (e.g. input/button)
66+
const target = (id as Event).target as HTMLInputElement | null;
67+
if (target && target.value !== undefined) {
68+
idNumber = Number(target.value);
69+
}
70+
}
71+
}
72+
73+
if (typeof idNumber === 'number' && !Number.isNaN(idNumber)) {
74+
this.store.deleteOne(idNumber);
75+
}
76+
}
3477
}

apps/angular/1-projection/src/app/data-access/city.store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { City } from '../model/city.model';
55
providedIn: 'root',
66
})
77
export class CityStore {
8-
private cities = signal<City[]>([]);
8+
public cities = signal<City[]>([]);
99

1010
addAll(cities: City[]) {
1111
this.cities.set(cities);
Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { NgOptimizedImage } from '@angular/common';
2-
import { Component, inject, input } from '@angular/core';
3-
import { randStudent, randTeacher } from '../../data-access/fake-http.service';
4-
import { StudentStore } from '../../data-access/student.store';
5-
import { TeacherStore } from '../../data-access/teacher.store';
6-
import { CardType } from '../../model/card.model';
1+
import { CommonModule, NgOptimizedImage } from '@angular/common';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
5+
input,
6+
output,
7+
} from '@angular/core';
78
import { ListItemComponent } from '../list-item/list-item.component';
89

910
@Component({
@@ -12,47 +13,34 @@ import { ListItemComponent } from '../list-item/list-item.component';
1213
<div
1314
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
1415
[class]="customClass()">
15-
@if (type() === CardType.TEACHER) {
16-
<img ngSrc="assets/img/teacher.png" width="200" height="200" />
17-
}
18-
@if (type() === CardType.STUDENT) {
19-
<img ngSrc="assets/img/student.webp" width="200" height="200" />
20-
}
16+
<img *ngIf="image()" [ngSrc]="image()!" width="200" height="200" />
2117
2218
<section>
23-
@for (item of list(); track item) {
19+
@for (item of list(); track item.id) {
2420
<app-list-item
25-
[name]="item.firstName"
21+
[label]="item.label"
2622
[id]="item.id"
27-
[type]="type()"></app-list-item>
23+
(delete)="delete.emit($event)"></app-list-item>
2824
}
2925
</section>
3026
3127
<button
3228
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
33-
(click)="addNewItem()">
29+
(click)="add.emit()">
3430
Add
3531
</button>
3632
</div>
3733
`,
38-
imports: [ListItemComponent, NgOptimizedImage],
34+
changeDetection: ChangeDetectionStrategy.OnPush,
35+
imports: [ListItemComponent, NgOptimizedImage, CommonModule],
3936
})
4037
export class CardComponent {
41-
private teacherStore = inject(TeacherStore);
42-
private studentStore = inject(StudentStore);
38+
// ожидаем входы через helper `input` (Angular signals-style)
39+
readonly list = input.required<any[]>(); // сигнал с массивом элементов
40+
readonly image = input<string | null>(null);
41+
readonly customClass = input<string>('');
4342

44-
readonly list = input<any[] | null>(null);
45-
readonly type = input.required<CardType>();
46-
readonly customClass = input('');
47-
48-
CardType = CardType;
49-
50-
addNewItem() {
51-
const type = this.type();
52-
if (type === CardType.TEACHER) {
53-
this.teacherStore.addOne(randTeacher());
54-
} else if (type === CardType.STUDENT) {
55-
this.studentStore.addOne(randStudent());
56-
}
57-
}
43+
// выходы
44+
readonly add = output<void>();
45+
readonly delete = output<number>();
5846
}
Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,26 @@
11
import {
22
ChangeDetectionStrategy,
33
Component,
4-
inject,
54
input,
5+
output,
66
} from '@angular/core';
7-
import { StudentStore } from '../../data-access/student.store';
8-
import { TeacherStore } from '../../data-access/teacher.store';
9-
import { CardType } from '../../model/card.model';
107

118
@Component({
129
selector: 'app-list-item',
1310
template: `
1411
<div class="border-grey-300 flex justify-between border px-2 py-1">
15-
{{ name() }}
16-
<button (click)="delete(id())">
12+
{{ label() }}
13+
14+
<button (click)="delete.emit(id())">
1715
<img class="h-5" src="assets/svg/trash.svg" />
1816
</button>
1917
</div>
2018
`,
2119
changeDetection: ChangeDetectionStrategy.OnPush,
2220
})
2321
export class ListItemComponent {
24-
private teacherStore = inject(TeacherStore);
25-
private studentStore = inject(StudentStore);
26-
2722
readonly id = input.required<number>();
28-
readonly name = input.required<string>();
29-
readonly type = input.required<CardType>();
23+
readonly label = input.required<string>();
3024

31-
delete(id: number) {
32-
const type = this.type();
33-
if (type === CardType.TEACHER) {
34-
this.teacherStore.deleteOne(id);
35-
} else if (type === CardType.STUDENT) {
36-
this.studentStore.deleteOne(id);
37-
}
38-
}
25+
readonly delete = output<number>();
3926
}

0 commit comments

Comments
 (0)