Skip to content

Commit e671959

Browse files
committed
feat(projection): refactor card component using slots
1 parent 175b7cf commit e671959

6 files changed

Lines changed: 106 additions & 42 deletions

File tree

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,63 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import { NgOptimizedImage } from '@angular/common';
2+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
3+
import { CityStore } from '../../data-access/city.store';
4+
import {
5+
FakeHttpService,
6+
randomCity,
7+
} from '../../data-access/fake-http.service';
8+
import { CardType } from '../../model/card.model';
9+
import { CardComponent } from '../../ui/card/card.component';
10+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
211

312
@Component({
413
selector: 'app-city-card',
5-
template: 'TODO City',
6-
imports: [],
14+
template: `
15+
<app-card [list]="cities()">
16+
<div card-header class="flex flex-col items-center">
17+
<img
18+
ngSrc="assets/img/city.png"
19+
width="200"
20+
height="200"
21+
alt="Cities list view banner"
22+
priority />
23+
</div>
24+
25+
<ng-template #itemTemplate let-item>
26+
<app-list-item
27+
[name]="item.name"
28+
[id]="item.id"
29+
[type]="cardType"
30+
(emitDelete)="deleteSingle($event)" />
31+
</ng-template>
32+
33+
<div card-action>
34+
<button
35+
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
36+
(click)="addNew()">
37+
Add
38+
</button>
39+
</div>
40+
</app-card>
41+
`,
42+
imports: [CardComponent, NgOptimizedImage, ListItemComponent],
743
changeDetection: ChangeDetectionStrategy.OnPush,
844
})
9-
export class CityCardComponent {}
45+
export class CityCardComponent {
46+
readonly cityStore = inject(CityStore);
47+
readonly http = inject(FakeHttpService);
48+
49+
cardType = CardType.CITY;
50+
readonly cities = this.cityStore.cities;
51+
52+
ngOnInit(): void {
53+
this.http.fetchCities$.subscribe((c) => this.cityStore.addAll(c));
54+
}
55+
56+
addNew(): void {
57+
this.cityStore.addOne(randomCity());
58+
}
59+
60+
deleteSingle(id: number) {
61+
this.cityStore.deleteOne(id);
62+
}
63+
}

apps/angular/1-projection/src/app/component/student-card/student-card.component.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ import {
1212
import { StudentStore } from '../../data-access/student.store';
1313
import { CardType } from '../../model/card.model';
1414
import { CardComponent } from '../../ui/card/card.component';
15+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
1516

1617
@Component({
1718
selector: 'app-student-card',
1819
template: `
19-
<app-card
20-
[list]="students()"
21-
[type]="cardType"
22-
customClass="bg-light-green">
20+
<app-card [list]="students()" customClass="bg-light-green">
2321
<div card-header class="flex flex-col items-center">
2422
<img
2523
ngSrc="assets/img/student.webp"
@@ -29,6 +27,14 @@ import { CardComponent } from '../../ui/card/card.component';
2927
priority />
3028
</div>
3129
30+
<ng-template #itemTemplate let-item>
31+
<app-list-item
32+
[name]="item.firstName"
33+
[id]="item.id"
34+
[type]="cardType"
35+
(emitDelete)="deleteSingle($event)" />
36+
</ng-template>
37+
3238
<div card-action>
3339
<button
3440
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
@@ -40,12 +46,12 @@ import { CardComponent } from '../../ui/card/card.component';
4046
`,
4147
styles: [
4248
`
43-
::ng-deep .bg-light-green {
44-
background-color: rgba(0, 250, 0, 0.1);
49+
:host {
50+
--card-bg: rgba(0, 250, 0, 0.1);
4551
}
4652
`,
4753
],
48-
imports: [CardComponent, NgOptimizedImage],
54+
imports: [CardComponent, NgOptimizedImage, ListItemComponent],
4955
changeDetection: ChangeDetectionStrategy.OnPush,
5056
})
5157
export class StudentCardComponent implements OnInit {
@@ -64,4 +70,8 @@ export class StudentCardComponent implements OnInit {
6470
addNew(): void {
6571
this.studentStore.addOne(randStudent());
6672
}
73+
74+
deleteSingle(id: number): void {
75+
this.studentStore.deleteOne(id);
76+
}
6777
}

apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,24 @@ import {
77
import { TeacherStore } from '../../data-access/teacher.store';
88
import { CardType } from '../../model/card.model';
99
import { CardComponent } from '../../ui/card/card.component';
10+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
1011

1112
@Component({
1213
selector: 'app-teacher-card',
1314
template: `
14-
<app-card [list]="teachers()" [type]="cardType" customClass="bg-light-red">
15+
<app-card [list]="teachers()" customClass="bg-light-red">
1516
<div card-header class="flex flex-col items-center">
1617
<img ngSrc="assets/img/teacher.png" width="200" height="200" alt="" />
1718
</div>
1819
20+
<ng-template #itemTemplate let-item>
21+
<app-list-item
22+
[name]="item.firstName"
23+
[id]="item.id"
24+
[type]="cardType"
25+
(emitDelete)="deleteSingle($event)" />
26+
</ng-template>
27+
1928
<div card-action>
2029
<button
2130
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
@@ -27,12 +36,12 @@ import { CardComponent } from '../../ui/card/card.component';
2736
`,
2837
styles: [
2938
`
30-
::ng-deep .bg-light-red {
31-
background-color: rgba(250, 0, 0, 0.1);
39+
:host {
40+
--card-bg: rgba(250, 0, 0, 0.1);
3241
}
3342
`,
3443
],
35-
imports: [CardComponent, NgOptimizedImage],
44+
imports: [CardComponent, NgOptimizedImage, ListItemComponent],
3645
})
3746
export class TeacherCardComponent implements OnInit {
3847
private readonly teacherStore = inject(TeacherStore);
@@ -50,4 +59,8 @@ export class TeacherCardComponent implements OnInit {
5059
addNew(): void {
5160
this.teacherStore.addOne(randTeacher());
5261
}
62+
63+
deleteSingle(id: number): void {
64+
this.teacherStore.deleteOne(id);
65+
}
5366
}

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 readonly cities = signal<City[]>([]);
99

1010
addAll(cities: City[]) {
1111
this.cities.set(cities);
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
import { Component, input } from '@angular/core';
2-
import { CardType } from '../../model/card.model';
3-
import { ListItemComponent } from '../list-item/list-item.component';
1+
import { NgTemplateOutlet } from '@angular/common';
2+
import { Component, ContentChild, input, TemplateRef } from '@angular/core';
43

54
@Component({
65
selector: 'app-card',
76
template: `
87
<div
98
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
9+
style="background-color: var(--card-bg, transparent)"
1010
[class]="customClass()">
1111
<ng-content select="[card-header]"></ng-content>
1212
1313
<section>
1414
@for (item of list(); track item) {
15-
<app-list-item
16-
[name]="item.firstName"
17-
[id]="item.id"
18-
[type]="type()"></app-list-item>
15+
<ng-container
16+
*ngTemplateOutlet="
17+
itemTemplate;
18+
context: { $implicit: item }
19+
"></ng-container>
1920
}
2021
</section>
2122
2223
<ng-content select="[card-action]"></ng-content>
2324
</div>
2425
`,
25-
imports: [ListItemComponent],
26+
imports: [NgTemplateOutlet],
2627
})
2728
export class CardComponent {
2829
readonly list = input<any[] | null>(null);
29-
readonly type = input.required<CardType>();
3030
readonly customClass = input('');
3131

32-
CardType = CardType;
32+
@ContentChild('itemTemplate') itemTemplate!: TemplateRef<any>;
3333
}
Lines changed: 3 additions & 16 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';
97
import { CardType } from '../../model/card.model';
108

119
@Component({
1210
selector: 'app-list-item',
1311
template: `
1412
<div class="flex justify-between border border-gray-300 px-2 py-1">
1513
{{ name() }}
16-
<button (click)="delete(id())">
14+
<button (click)="emitDelete.emit(id())">
1715
<img class="h-5" src="assets/svg/trash.svg" alt="trash" />
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>();
2823
readonly name = input.required<string>();
2924
readonly type = input.required<CardType>();
30-
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 emitDelete = output<number>();
3926
}

0 commit comments

Comments
 (0)