Skip to content

Commit 13334b1

Browse files
committed
feat: implement solution for content projection
1 parent 418a2fd commit 13334b1

6 files changed

Lines changed: 116 additions & 64 deletions

File tree

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
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 { CardType } from '../../model/card.model';
8+
import { CardComponent } from '../../ui/card/card.component';
29

310
@Component({
411
selector: 'app-city-card',
5-
template: 'TODO City',
6-
imports: [],
12+
template: `
13+
<app-card
14+
[list]="cities()"
15+
[type]="cardType"
16+
(addItem)="addNewItem()"
17+
(deleteItem)="deleteItem($event)"
18+
pictureType="png"
19+
customClass="bg-light-green" />
20+
`,
21+
imports: [CardComponent],
722
changeDetection: ChangeDetectionStrategy.OnPush,
823
})
9-
export class CityCardComponent {}
24+
export class CityCardComponent {
25+
private http = inject(FakeHttpService);
26+
private store = inject(CityStore);
27+
readonly cardType = CardType.CITY;
28+
readonly cities = this.store.cities;
29+
30+
ngOnInit(): void {
31+
this.http.fetchCities$.subscribe((c) => this.store.addAll(c));
32+
}
33+
34+
addNewItem() {
35+
this.store.addOne(randomCity());
36+
}
37+
38+
deleteItem(id: number) {
39+
this.store.deleteOne(id);
40+
}
41+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {
44
inject,
55
OnInit,
66
} from '@angular/core';
7-
import { FakeHttpService } from '../../data-access/fake-http.service';
7+
import {
8+
FakeHttpService,
9+
randStudent,
10+
} from '../../data-access/fake-http.service';
811
import { StudentStore } from '../../data-access/student.store';
912
import { CardType } from '../../model/card.model';
1013
import { CardComponent } from '../../ui/card/card.component';
@@ -15,6 +18,9 @@ import { CardComponent } from '../../ui/card/card.component';
1518
<app-card
1619
[list]="students()"
1720
[type]="cardType"
21+
(addItem)="addNewItem()"
22+
(deleteItem)="deleteItem($event)"
23+
pictureType="webp"
1824
customClass="bg-light-green" />
1925
`,
2026
styles: [
@@ -30,11 +36,18 @@ import { CardComponent } from '../../ui/card/card.component';
3036
export class StudentCardComponent implements OnInit {
3137
private http = inject(FakeHttpService);
3238
private store = inject(StudentStore);
33-
3439
students = this.store.students;
3540
cardType = CardType.STUDENT;
3641

3742
ngOnInit(): void {
3843
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
3944
}
45+
46+
addNewItem() {
47+
this.store.addOne(randStudent());
48+
}
49+
50+
deleteItem(id: number) {
51+
this.store.deleteOne(id);
52+
}
4053
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Component, inject, OnInit } from '@angular/core';
2-
import { FakeHttpService } from '../../data-access/fake-http.service';
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';
@@ -10,6 +13,9 @@ import { CardComponent } from '../../ui/card/card.component';
1013
<app-card
1114
[list]="teachers()"
1215
[type]="cardType"
16+
(addItem)="addNewItem()"
17+
(deleteItem)="deleteItem($event)"
18+
pictureType="png"
1319
customClass="bg-light-red"></app-card>
1420
`,
1521
styles: [
@@ -31,4 +37,12 @@ export class TeacherCardComponent implements OnInit {
3137
ngOnInit(): void {
3238
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
3339
}
40+
41+
addNewItem() {
42+
this.store.addOne(randTeacher());
43+
}
44+
45+
deleteItem(id: number) {
46+
this.store.deleteOne(id);
47+
}
3448
}

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: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,57 @@
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';
1+
import { NgOptimizedImage, NgTemplateOutlet } from '@angular/common';
2+
import { Component, input, output } from '@angular/core';
63
import { CardType } from '../../model/card.model';
74
import { ListItemComponent } from '../list-item/list-item.component';
85

96
@Component({
107
selector: 'app-card',
118
template: `
12-
<div
13-
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
14-
[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-
}
9+
<ng-template #card let-cardTypeVal="cardType">
10+
<div
11+
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
12+
[class]="customClass()">
13+
<img
14+
ngSrc="assets/img/{{ CardType[cardTypeVal()].toLowerCase() }}.{{
15+
this.pictureType()
16+
}}"
17+
width="200"
18+
height="200"
19+
priority />
20+
<section>
21+
@for (item of list(); track item) {
22+
<app-list-item
23+
[name]="item.name"
24+
[firstName]="item.firstName"
25+
[lastName]="item.lastName"
26+
[id]="item.id"
27+
[type]="type()"
28+
(deleteItem)="delete($event)" />
29+
}
30+
</section>
2131
22-
<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-
}
29-
</section>
32+
<button
33+
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
34+
(click)="addItem.emit()">
35+
Add
36+
</button>
37+
</div>
38+
</ng-template>
3039
31-
<button
32-
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
33-
(click)="addNewItem()">
34-
Add
35-
</button>
36-
</div>
40+
<ng-container *ngTemplateOutlet="card; context: ctx"></ng-container>
3741
`,
38-
imports: [ListItemComponent, NgOptimizedImage],
42+
imports: [ListItemComponent, NgOptimizedImage, NgTemplateOutlet],
3943
})
4044
export class CardComponent {
41-
private teacherStore = inject(TeacherStore);
42-
private studentStore = inject(StudentStore);
43-
4445
readonly list = input<any[] | null>(null);
4546
readonly type = input.required<CardType>();
4647
readonly customClass = input('');
47-
48+
readonly pictureType = input.required<string>();
49+
addItem = output<void>();
50+
deleteItem = output<number>();
51+
readonly ctx = { cardType: this.type };
4852
CardType = CardType;
4953

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-
}
54+
delete(id: number) {
55+
this.deleteItem.emit(id);
5756
}
5857
}
Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,33 @@
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="border-grey-300 flex justify-between border px-2 py-1">
13+
{{ firstName() }} {{ lastName() }}
1514
{{ name() }}
16-
<button (click)="delete(id())">
15+
<button (click)="onDelete()">
1716
<img class="h-5" src="assets/svg/trash.svg" />
1817
</button>
1918
</div>
2019
`,
2120
changeDetection: ChangeDetectionStrategy.OnPush,
2221
})
2322
export class ListItemComponent {
24-
private teacherStore = inject(TeacherStore);
25-
private studentStore = inject(StudentStore);
26-
2723
readonly id = input.required<number>();
28-
readonly name = input.required<string>();
29-
readonly type = input.required<CardType>();
24+
readonly type = input<CardType>();
25+
readonly name = input<string>();
26+
readonly firstName = input<string>('');
27+
readonly lastName = input<string>('');
28+
deleteItem = output<number>();
3029

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-
}
30+
onDelete() {
31+
this.deleteItem.emit(this.id());
3832
}
3933
}

0 commit comments

Comments
 (0)