Skip to content

Commit b52f4b4

Browse files
committed
feat: resolve projection of Answer:1
1 parent c0af4fa commit b52f4b4

File tree

7 files changed

+159
-102
lines changed

7 files changed

+159
-102
lines changed
Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,50 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, OnInit, Signal } from '@angular/core';
2+
import { toSignal } from '@angular/core/rxjs-interop';
3+
import { CityStore } from '../../data-access/city.store';
4+
import {
5+
FakeHttpService,
6+
randomCity,
7+
} from '../../data-access/fake-http.service';
8+
import { City } from '../../model/city.model';
9+
import { CardComponent } from '../../ui/card/card.component';
10+
import { ListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive';
11+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
212

313
@Component({
414
selector: 'app-city-card',
5-
template: 'TODO City',
15+
template: `
16+
<app-card [list]="cities()" (add)="addCity()">
17+
<img src="assets/img/city.png" width="200px" alt="student" />
18+
19+
<ng-template appListItemTemplate let-city>
20+
<app-list-item (deleteItem)="deleteCity(city.id)">
21+
{{ city.name }}
22+
</app-list-item>
23+
</ng-template>
24+
</app-card>
25+
`,
626
standalone: true,
7-
imports: [],
27+
imports: [CardComponent, ListItemTemplateDirective, ListItemComponent],
828
})
929
export class CityCardComponent implements OnInit {
10-
constructor() {}
30+
cities: Signal<City[]> = toSignal(this.store.cities$, {
31+
initialValue: [],
32+
});
33+
34+
constructor(
35+
private http: FakeHttpService,
36+
private store: CityStore,
37+
) {}
38+
39+
ngOnInit(): void {
40+
this.http.fetchCities$.subscribe((s) => this.store.addAll(s));
41+
}
42+
43+
addCity() {
44+
this.store.addOne(randomCity());
45+
}
1146

12-
ngOnInit(): void {}
47+
deleteCity(id: number) {
48+
this.store.deleteOne(id);
49+
}
1350
}
Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
11
import { Component, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
2+
import { toSignal } from '@angular/core/rxjs-interop';
3+
import {
4+
FakeHttpService,
5+
randStudent,
6+
} from '../../data-access/fake-http.service';
37
import { StudentStore } from '../../data-access/student.store';
4-
import { CardType } from '../../model/card.model';
5-
import { Student } from '../../model/student.model';
68
import { CardComponent } from '../../ui/card/card.component';
9+
import { ListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive';
10+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
711

812
@Component({
913
selector: 'app-student-card',
1014
template: `
11-
<app-card
12-
[list]="students"
13-
[type]="cardType"
14-
customClass="bg-light-green"></app-card>
15+
<app-card class="bg-light-green" [list]="students()" (add)="addStudent()">
16+
<img src="assets/img/student.webp" width="200px" alt="student" />
17+
18+
<ng-template appListItemTemplate let-student>
19+
<app-list-item (deleteItem)="deleteStudent(student.id)">
20+
{{ student.firstName }}
21+
</app-list-item>
22+
</ng-template>
23+
</app-card>
1524
`,
1625
standalone: true,
1726
styles: [
1827
`
19-
::ng-deep .bg-light-green {
28+
.bg-light-green {
2029
background-color: rgba(0, 250, 0, 0.1);
2130
}
2231
`,
2332
],
24-
imports: [CardComponent],
33+
imports: [CardComponent, ListItemComponent, ListItemTemplateDirective],
2534
})
2635
export class StudentCardComponent implements OnInit {
27-
students: Student[] = [];
28-
cardType = CardType.STUDENT;
36+
students = toSignal(this.store.students$, {
37+
initialValue: [],
38+
});
2939

3040
constructor(
3141
private http: FakeHttpService,
@@ -34,7 +44,13 @@ export class StudentCardComponent implements OnInit {
3444

3545
ngOnInit(): void {
3646
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
47+
}
48+
49+
addStudent() {
50+
this.store.addOne(randStudent());
51+
}
3752

38-
this.store.students$.subscribe((s) => (this.students = s));
53+
deleteStudent(id: number) {
54+
this.store.deleteOne(id);
3955
}
4056
}
Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,48 @@
1-
import { Component, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
OnInit,
5+
Signal,
6+
} from '@angular/core';
7+
import { toSignal } from '@angular/core/rxjs-interop';
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 { Teacher } from '../../model/teacher.model';
614
import { CardComponent } from '../../ui/card/card.component';
15+
import { ListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive';
16+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
717

818
@Component({
919
selector: 'app-teacher-card',
1020
template: `
11-
<app-card
12-
[list]="teachers"
13-
[type]="cardType"
14-
customClass="bg-light-red"></app-card>
21+
<app-card class="bg-light-red" [list]="teachers()" (add)="addTeacher()">
22+
<img src="assets/img/teacher.png" width="200px" alt="teacher" />
23+
24+
<ng-template appListItemTemplate let-teacher>
25+
<app-list-item (deleteItem)="deleteTeacher(teacher.id)">
26+
{{ teacher.firstName }}
27+
</app-list-item>
28+
</ng-template>
29+
</app-card>
1530
`,
1631
styles: [
1732
`
18-
::ng-deep .bg-light-red {
33+
.bg-light-red {
1934
background-color: rgba(250, 0, 0, 0.1);
2035
}
2136
`,
2237
],
2338
standalone: true,
24-
imports: [CardComponent],
39+
changeDetection: ChangeDetectionStrategy.OnPush,
40+
imports: [CardComponent, ListItemComponent, ListItemTemplateDirective],
2541
})
2642
export class TeacherCardComponent implements OnInit {
27-
teachers: Teacher[] = [];
28-
cardType = CardType.TEACHER;
43+
teachers: Signal<Teacher[]> = toSignal(this.store.teachers$, {
44+
initialValue: [],
45+
});
2946

3047
constructor(
3148
private http: FakeHttpService,
@@ -34,7 +51,13 @@ export class TeacherCardComponent implements OnInit {
3451

3552
ngOnInit(): void {
3653
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
54+
}
55+
56+
addTeacher() {
57+
this.store.addOne(randTeacher());
58+
}
3759

38-
this.store.teachers$.subscribe((t) => (this.teachers = t));
60+
deleteTeacher(id: number) {
61+
this.store.deleteOne(id);
3962
}
4063
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export class CityStore {
1313
this.cities.next(cities);
1414
}
1515

16-
addOne(student: City) {
17-
this.cities.next([...this.cities.value, student]);
16+
addOne(city: City) {
17+
this.cities.next([...this.cities.value, city]);
1818
}
1919

2020
deleteOne(id: number) {
Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,48 @@
1-
import { NgFor, NgIf } from '@angular/common';
2-
import { Component, 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 { NgTemplateOutlet } from '@angular/common';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
5+
contentChild,
6+
input,
7+
output,
8+
} from '@angular/core';
9+
import { ListItemTemplateDirective } from '../list-item/list-item-template.directive';
710
import { ListItemComponent } from '../list-item/list-item.component';
811

912
@Component({
1013
selector: 'app-card',
1114
template: `
12-
<div
13-
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
14-
[class]="customClass">
15-
<img
16-
*ngIf="type === CardType.TEACHER"
17-
src="assets/img/teacher.png"
18-
width="200px" />
19-
<img
20-
*ngIf="type === CardType.STUDENT"
21-
src="assets/img/student.webp"
22-
width="200px" />
15+
<ng-content select="img" />
2316
24-
<section>
25-
<app-list-item
26-
*ngFor="let item of list"
27-
[name]="item.firstName"
28-
[id]="item.id"
29-
[type]="type"></app-list-item>
30-
</section>
17+
<section>
18+
@for (item of list(); track item) {
19+
<ng-template
20+
[ngTemplateOutlet]="itemTemplate()?.templateRef ?? null"
21+
[ngTemplateOutletContext]="{ $implicit: item }" />
22+
}
23+
</section>
3124
32-
<button
33-
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
34-
(click)="addNewItem()">
35-
Add
36-
</button>
37-
</div>
25+
<button
26+
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
27+
(click)="addNewItem()">
28+
Add
29+
</button>
3830
`,
3931
standalone: true,
40-
imports: [NgIf, NgFor, ListItemComponent],
32+
imports: [ListItemComponent, NgTemplateOutlet],
33+
changeDetection: ChangeDetectionStrategy.OnPush,
34+
host: {
35+
class: 'flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4',
36+
},
4137
})
42-
export class CardComponent {
43-
@Input() list: any[] | null = null;
44-
@Input() type!: CardType;
45-
@Input() customClass = '';
38+
export class CardComponent<T> {
39+
list = input<T[]>([]);
4640

47-
CardType = CardType;
41+
itemTemplate = contentChild(ListItemTemplateDirective);
4842

49-
constructor(
50-
private teacherStore: TeacherStore,
51-
private studentStore: StudentStore,
52-
) {}
43+
add = output();
5344

5445
addNewItem() {
55-
if (this.type === CardType.TEACHER) {
56-
this.teacherStore.addOne(randTeacher());
57-
} else if (this.type === CardType.STUDENT) {
58-
this.studentStore.addOne(randStudent());
59-
}
46+
this.add.emit();
6047
}
6148
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Directive, TemplateRef } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[appListItemTemplate]',
5+
standalone: true,
6+
})
7+
export class ListItemTemplateDirective {
8+
constructor(public templateRef: TemplateRef<unknown>) {}
9+
}
Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,20 @@
1-
import { Component, Input } from '@angular/core';
2-
import { StudentStore } from '../../data-access/student.store';
3-
import { TeacherStore } from '../../data-access/teacher.store';
4-
import { CardType } from '../../model/card.model';
1+
import { ChangeDetectionStrategy, Component, output } from '@angular/core';
52

63
@Component({
74
selector: 'app-list-item',
85
template: `
9-
<div class="border-grey-300 flex justify-between border px-2 py-1">
10-
{{ name }}
11-
<button (click)="delete(id)">
12-
<img class="h-5" src="assets/svg/trash.svg" />
13-
</button>
14-
</div>
6+
<ng-content />
7+
8+
<button (click)="deleteItem.emit()">
9+
<img class="h-5" src="assets/svg/trash.svg" alt="delete" />
10+
</button>
1511
`,
1612
standalone: true,
13+
changeDetection: ChangeDetectionStrategy.OnPush,
14+
host: {
15+
class: 'border-grey-300 flex justify-between border px-2 py-1',
16+
},
1717
})
1818
export class ListItemComponent {
19-
@Input() id!: number;
20-
@Input() name!: string;
21-
@Input() type!: CardType;
22-
23-
constructor(
24-
private teacherStore: TeacherStore,
25-
private studentStore: StudentStore,
26-
) {}
27-
28-
delete(id: number) {
29-
if (this.type === CardType.TEACHER) {
30-
this.teacherStore.deleteOne(id);
31-
} else if (this.type === CardType.STUDENT) {
32-
this.studentStore.deleteOne(id);
33-
}
34-
}
19+
deleteItem = output();
3520
}

0 commit comments

Comments
 (0)