Skip to content

Commit 6278a61

Browse files
committed
feat(challenge 1): card component
1 parent 543770b commit 6278a61

6 files changed

Lines changed: 210 additions & 83 deletions

File tree

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,84 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import { NgOptimizedImage } from '@angular/common';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
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 {
14+
CardActions,
15+
CardComponent,
16+
CardContent,
17+
CardImage,
18+
} from '../../ui/card/card.component';
19+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
220

321
@Component({
422
selector: 'app-city-card',
5-
template: 'TODO City',
6-
imports: [],
23+
template: `
24+
<app-card customClass="bg-light-yellow">
25+
<img
26+
app-card-image
27+
ngSrc="assets/img/city.png"
28+
width="200"
29+
height="200"
30+
alt="" />
31+
32+
<app-card-content>
33+
@for (item of cities(); track item) {
34+
<app-list-item
35+
[name]="item.name"
36+
[id]="item.id"
37+
(delete)="deleteItem($event)" />
38+
}
39+
</app-card-content>
40+
41+
<app-card-actions>
42+
<button
43+
class="w-full rounded-sm border border-blue-500 bg-blue-300 p-2"
44+
(click)="addNewItem()">
45+
Add
46+
</button>
47+
</app-card-actions>
48+
</app-card>
49+
`,
50+
styles: [
51+
`
52+
::ng-deep .bg-light-yellow {
53+
background-color: rgba(244, 246, 104, 0.1);
54+
}
55+
`,
56+
],
57+
imports: [
58+
NgOptimizedImage,
59+
CardComponent,
60+
CardImage,
61+
CardContent,
62+
CardActions,
63+
ListItemComponent,
64+
],
765
changeDetection: ChangeDetectionStrategy.OnPush,
866
})
9-
export class CityCardComponent {}
67+
export class CityCardComponent implements OnInit {
68+
private readonly http = inject(FakeHttpService);
69+
private readonly store = inject(CityStore);
70+
71+
cities = this.store.cities;
72+
73+
ngOnInit(): void {
74+
this.http.fetchCities$.subscribe((c) => this.store.addAll(c));
75+
}
76+
77+
deleteItem(id: number) {
78+
this.store.deleteOne(id);
79+
}
80+
81+
addNewItem() {
82+
this.store.addOne(randomCity());
83+
}
84+
}
Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,51 @@
1+
import { NgOptimizedImage } from '@angular/common';
12
import {
23
ChangeDetectionStrategy,
34
Component,
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';
10-
import { CardComponent } from '../../ui/card/card.component';
13+
import {
14+
CardActions,
15+
CardComponent,
16+
CardContent,
17+
CardImage,
18+
} from '../../ui/card/card.component';
19+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
1120

1221
@Component({
1322
selector: 'app-student-card',
1423
template: `
15-
<app-card
16-
[list]="students()"
17-
[type]="cardType"
18-
customClass="bg-light-green" />
24+
<app-card customClass="bg-light-green">
25+
<img
26+
app-card-image
27+
ngSrc="assets/img/student.webp"
28+
width="200"
29+
height="200"
30+
alt="" />
31+
32+
<app-card-content>
33+
@for (item of students(); track item) {
34+
<app-list-item
35+
[name]="item.firstName"
36+
[id]="item.id"
37+
(delete)="deleteItem($event)" />
38+
}
39+
</app-card-content>
40+
41+
<app-card-actions>
42+
<button
43+
class="w-full rounded-sm border border-blue-500 bg-blue-300 p-2"
44+
(click)="addNewItem()">
45+
Add
46+
</button>
47+
</app-card-actions>
48+
</app-card>
1949
`,
2050
styles: [
2151
`
@@ -24,17 +54,31 @@ import { CardComponent } from '../../ui/card/card.component';
2454
}
2555
`,
2656
],
27-
imports: [CardComponent],
57+
imports: [
58+
NgOptimizedImage,
59+
CardComponent,
60+
CardImage,
61+
CardContent,
62+
CardActions,
63+
ListItemComponent,
64+
],
2865
changeDetection: ChangeDetectionStrategy.OnPush,
2966
})
3067
export class StudentCardComponent implements OnInit {
31-
private http = inject(FakeHttpService);
32-
private store = inject(StudentStore);
68+
private readonly http = inject(FakeHttpService);
69+
private readonly store = inject(StudentStore);
3370

3471
students = this.store.students;
35-
cardType = CardType.STUDENT;
3672

3773
ngOnInit(): void {
3874
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
3975
}
76+
77+
deleteItem(id: number) {
78+
this.store.deleteOne(id);
79+
}
80+
81+
addNewItem() {
82+
this.store.addOne(randStudent());
83+
}
4084
}
Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1+
import { NgOptimizedImage } from '@angular/common';
12
import { Component, inject, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
3+
import {
4+
FakeHttpService,
5+
randTeacher,
6+
} from '../../data-access/fake-http.service';
37
import { TeacherStore } from '../../data-access/teacher.store';
4-
import { CardType } from '../../model/card.model';
5-
import { CardComponent } from '../../ui/card/card.component';
8+
import {
9+
CardActions,
10+
CardComponent,
11+
CardContent,
12+
CardImage,
13+
} from '../../ui/card/card.component';
14+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
615

716
@Component({
817
selector: 'app-teacher-card',
918
template: `
10-
<app-card
11-
[list]="teachers()"
12-
[type]="cardType"
13-
customClass="bg-light-red"></app-card>
19+
<app-card customClass="bg-light-red">
20+
<img
21+
app-card-image
22+
ngSrc="assets/img/teacher.png"
23+
width="200"
24+
height="200"
25+
alt="" />
26+
27+
<app-card-content>
28+
@for (item of teachers(); track item) {
29+
<app-list-item
30+
[name]="item.firstName"
31+
[id]="item.id"
32+
(delete)="deleteItem($event)" />
33+
}
34+
</app-card-content>
35+
36+
<app-card-actions>
37+
<button
38+
class="w-full rounded-sm border border-blue-500 bg-blue-300 p-2"
39+
(click)="addNewItem()">
40+
Add
41+
</button>
42+
</app-card-actions>
43+
</app-card>
1444
`,
1545
styles: [
1646
`
@@ -19,16 +49,30 @@ import { CardComponent } from '../../ui/card/card.component';
1949
}
2050
`,
2151
],
22-
imports: [CardComponent],
52+
imports: [
53+
NgOptimizedImage,
54+
CardComponent,
55+
CardImage,
56+
CardContent,
57+
CardActions,
58+
ListItemComponent,
59+
],
2360
})
2461
export class TeacherCardComponent implements OnInit {
2562
private http = inject(FakeHttpService);
2663
private store = inject(TeacherStore);
2764

2865
teachers = this.store.teachers;
29-
cardType = CardType.TEACHER;
3066

3167
ngOnInit(): void {
3268
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
3369
}
70+
71+
deleteItem(id: number) {
72+
this.store.deleteOne(id);
73+
}
74+
75+
addNewItem() {
76+
this.store.addOne(randTeacher());
77+
}
3478
}

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: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,36 @@
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';
7-
import { ListItemComponent } from '../list-item/list-item.component';
1+
import { Component, Directive, input } from '@angular/core';
2+
3+
@Directive({
4+
selector: 'img[app-card-image]',
5+
})
6+
export class CardImage {}
7+
8+
@Directive({
9+
selector: 'app-card-content',
10+
})
11+
export class CardContent {}
12+
13+
@Directive({
14+
selector: 'app-card-actions',
15+
})
16+
export class CardActions {}
817

918
@Component({
1019
selector: 'app-card',
1120
template: `
1221
<div
1322
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
1423
[class]="customClass()">
15-
@if (type() === CardType.TEACHER) {
16-
<img ngSrc="assets/img/teacher.png" width="200" height="200" alt="" />
17-
}
18-
@if (type() === CardType.STUDENT) {
19-
<img ngSrc="assets/img/student.webp" width="200" height="200" alt="" />
20-
}
24+
<ng-content select="[app-card-image]"></ng-content>
2125
2226
<section>
23-
@for (item of list(); track item) {
24-
<app-list-item
25-
[name]="item.firstName"
26-
[id]="item.id"
27-
[type]="type()"></app-list-item>
28-
}
27+
<ng-content select="app-card-content"></ng-content>
2928
</section>
3029
31-
<button
32-
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
33-
(click)="addNewItem()">
34-
Add
35-
</button>
30+
<ng-content select="app-card-actions"></ng-content>
3631
</div>
3732
`,
38-
imports: [ListItemComponent, NgOptimizedImage],
3933
})
4034
export class CardComponent {
41-
private teacherStore = inject(TeacherStore);
42-
private studentStore = inject(StudentStore);
43-
44-
readonly list = input<any[] | null>(null);
45-
readonly type = input.required<CardType>();
4635
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-
}
5836
}

0 commit comments

Comments
 (0)