Skip to content

Commit 6be8c0c

Browse files
committed
feat: refactor card and list component to project elements from parent
1 parent 805b53b commit 6be8c0c

6 files changed

Lines changed: 140 additions & 84 deletions

File tree

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

310
@Component({
411
selector: 'app-city-card',
5-
template: 'TODO City',
6-
imports: [],
12+
template: `
13+
<app-card [list]="cities()">
14+
<img
15+
ngProjectAs="card-img"
16+
src="assets/img/city.png"
17+
width="200"
18+
height="200" />
19+
<ng-template #listTemplate let-city>
20+
<app-list-item>
21+
{{ city.name }}
22+
<button (click)="deleteCity(city.id)" ngProjectAs="delete-button">
23+
<img class="h-5" src="assets/svg/trash.svg" alt="icon trash" />
24+
</button>
25+
</app-list-item>
26+
</ng-template>
27+
<button
28+
(click)="addCity()"
29+
ngProjectAs="add-button"
30+
class="rounded-sm border border-blue-500 bg-blue-300 p-2">
31+
Add
32+
</button>
33+
</app-card>
34+
`,
35+
imports: [CardComponent, ListItemComponent],
736
changeDetection: ChangeDetectionStrategy.OnPush,
837
})
9-
export class CityCardComponent {}
38+
export class CityCardComponent {
39+
private http = inject(FakeHttpService);
40+
private store = inject(CityStore);
41+
42+
cities = this.store.cities;
43+
44+
ngOnInit(): void {
45+
this.http.fetchCities$.subscribe((s) => this.store.addAll(s));
46+
}
47+
48+
deleteCity(id: number) {
49+
this.store.deleteOne(id);
50+
}
51+
addCity() {
52+
this.store.addOne(randomCity());
53+
}
54+
}

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,43 @@ import {
33
Component,
44
inject,
55
OnInit,
6+
ViewEncapsulation,
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';
913
import { CardType } from '../../model/card.model';
1014
import { CardComponent } from '../../ui/card/card.component';
15+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
1116

1217
@Component({
1318
selector: 'app-student-card',
1419
template: `
15-
<app-card
16-
[list]="students()"
17-
[type]="cardType"
18-
customClass="bg-light-green" />
20+
<app-card [list]="students()" customClass="bg-light-green">
21+
<img
22+
ngProjectAs="card-img"
23+
src="assets/img/student.webp"
24+
width="200"
25+
height="200" />
26+
<ng-template #listTemplate let-student>
27+
<app-list-item>
28+
{{ student.firstName }}
29+
<button
30+
(click)="deleteStudent(student.id)"
31+
ngProjectAs="delete-button">
32+
<img class="h-5" src="assets/svg/trash.svg" alt="icon trash" />
33+
</button>
34+
</app-list-item>
35+
</ng-template>
36+
<button
37+
(click)="addStudent()"
38+
ngProjectAs="add-button"
39+
class="rounded-sm border border-blue-500 bg-blue-300 p-2">
40+
Add
41+
</button>
42+
</app-card>
1943
`,
2044
styles: [
2145
`
@@ -24,7 +48,8 @@ import { CardComponent } from '../../ui/card/card.component';
2448
}
2549
`,
2650
],
27-
imports: [CardComponent],
51+
encapsulation: ViewEncapsulation.None,
52+
imports: [CardComponent, ListItemComponent],
2853
changeDetection: ChangeDetectionStrategy.OnPush,
2954
})
3055
export class StudentCardComponent implements OnInit {
@@ -37,4 +62,11 @@ export class StudentCardComponent implements OnInit {
3762
ngOnInit(): void {
3863
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
3964
}
65+
deleteStudent(id: number) {
66+
this.store.deleteOne(id);
67+
}
68+
69+
addStudent() {
70+
this.store.addOne(randStudent());
71+
}
4072
}
Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
1-
import { Component, inject, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
1+
import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
2+
import {
3+
FakeHttpService,
4+
randTeacher,
5+
} from '../../data-access/fake-http.service';
36
import { TeacherStore } from '../../data-access/teacher.store';
47
import { CardType } from '../../model/card.model';
58
import { CardComponent } from '../../ui/card/card.component';
9+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
610

711
@Component({
812
selector: 'app-teacher-card',
913
template: `
10-
<app-card
11-
[list]="teachers()"
12-
[type]="cardType"
13-
customClass="bg-light-red"></app-card>
14+
<app-card [list]="teachers()" customClass="bg-light-red">
15+
<img
16+
ngProjectAs="card-img"
17+
src="assets/img/teacher.png"
18+
width="200"
19+
height="200" />
20+
<ng-template #listTemplate let-teacher>
21+
<app-list-item>
22+
{{ teacher.firstName }}
23+
<button
24+
(click)="deleteTeacher(teacher.id)"
25+
ngProjectAs="delete-button">
26+
<img class="h-5" src="assets/svg/trash.svg" alt="icon trash" />
27+
</button>
28+
</app-list-item>
29+
</ng-template>
30+
<button
31+
(click)="addTeacher()"
32+
ngProjectAs="add-button"
33+
class="rounded-sm border border-blue-500 bg-blue-300 p-2">
34+
Add
35+
</button>
36+
</app-card>
1437
`,
1538
styles: [
1639
`
17-
::ng-deep .bg-light-red {
40+
.bg-light-red {
1841
background-color: rgba(250, 0, 0, 0.1);
1942
}
2043
`,
2144
],
22-
imports: [CardComponent],
45+
encapsulation: ViewEncapsulation.None,
46+
imports: [CardComponent, ListItemComponent],
2347
})
2448
export class TeacherCardComponent implements OnInit {
2549
private http = inject(FakeHttpService);
@@ -31,4 +55,11 @@ export class TeacherCardComponent implements OnInit {
3155
ngOnInit(): void {
3256
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
3357
}
58+
deleteTeacher(id: number) {
59+
this.store.deleteOne(id);
60+
}
61+
62+
addTeacher() {
63+
this.store.addOne(randTeacher());
64+
}
3465
}

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: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
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 { NgTemplateOutlet } from '@angular/common';
2+
import { Component, ContentChild, input, TemplateRef } from '@angular/core';
73
import { ListItemComponent } from '../list-item/list-item.component';
84

95
@Component({
@@ -12,47 +8,23 @@ import { ListItemComponent } from '../list-item/list-item.component';
128
<div
139
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
1410
[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-
}
11+
<ng-content select="card-img"></ng-content>
2112
2213
<section>
2314
@for (item of list(); track item) {
24-
<app-list-item
25-
[name]="item.firstName"
26-
[id]="item.id"
27-
[type]="type()"></app-list-item>
15+
<ng-container
16+
[ngTemplateOutlet]="listTemplate"
17+
[ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
2818
}
2919
</section>
3020
31-
<button
32-
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
33-
(click)="addNewItem()">
34-
Add
35-
</button>
21+
<ng-content select="add-button"></ng-content>
3622
</div>
3723
`,
38-
imports: [ListItemComponent, NgOptimizedImage],
24+
imports: [NgTemplateOutlet, ListItemComponent],
3925
})
4026
export class CardComponent {
41-
private teacherStore = inject(TeacherStore);
42-
private studentStore = inject(StudentStore);
43-
4427
readonly list = input<any[] | null>(null);
45-
readonly type = input.required<CardType>();
4628
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-
}
29+
@ContentChild('listTemplate') listTemplate!: TemplateRef<ListItemComponent>;
5830
}
Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,16 @@
1-
import {
2-
ChangeDetectionStrategy,
3-
Component,
4-
inject,
5-
input,
6-
} 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';
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
102

113
@Component({
124
selector: 'app-list-item',
135
template: `
146
<div class="border-grey-300 flex justify-between border px-2 py-1">
15-
{{ name() }}
16-
<button (click)="delete(id())">
17-
<img class="h-5" src="assets/svg/trash.svg" />
18-
</button>
7+
<ng-content></ng-content>
8+
<ng-content select="delete-button"></ng-content>
199
</div>
2010
`,
2111
standalone: true,
2212
changeDetection: ChangeDetectionStrategy.OnPush,
2313
})
2414
export class ListItemComponent {
25-
private teacherStore = inject(TeacherStore);
26-
private studentStore = inject(StudentStore);
27-
28-
readonly id = input.required<number>();
29-
readonly name = input.required<string>();
30-
readonly type = input.required<CardType>();
31-
32-
delete(id: number) {
33-
const type = this.type();
34-
if (type === CardType.TEACHER) {
35-
this.teacherStore.deleteOne(id);
36-
} else if (type === CardType.STUDENT) {
37-
this.studentStore.deleteOne(id);
38-
}
39-
}
15+
constructor() {}
4016
}

0 commit comments

Comments
 (0)