Skip to content

Commit fd9cb73

Browse files
committed
feat: angular projection
1 parent b263d0f commit fd9cb73

File tree

7 files changed

+158
-71
lines changed

7 files changed

+158
-71
lines changed
Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,53 @@
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 { CardListItemTemplateDirective } from '../../ui/card/card-list-item-template.directive';
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
16+
[list]="cities()"
17+
customClass="bg-light-blue"
18+
(onAddNewItem)="addNewCity()">
19+
<img ngSrc="assets/img/city.png" width="200" height="200" />
20+
21+
<ng-template appCardListItem let-item="item">
22+
<app-list-item
23+
[name]="item.name"
24+
[id]="item.id"
25+
(onDelete)="deleteCity(item.id)"></app-list-item>
26+
</ng-template>
27+
</app-card>
28+
`,
29+
imports: [
30+
CardComponent,
31+
CardListItemTemplateDirective,
32+
ListItemComponent,
33+
NgOptimizedImage,
34+
],
735
changeDetection: ChangeDetectionStrategy.OnPush,
836
})
9-
export class CityCardComponent {}
37+
export class CityCardComponent {
38+
private http = inject(FakeHttpService);
39+
store = inject(CityStore);
40+
cities = this.store.cities;
41+
42+
ngOnInit(): void {
43+
this.http.fetchCities$.subscribe((s) => this.store.addAll(s));
44+
}
45+
46+
addNewCity() {
47+
this.store.addOne(randomCity());
48+
}
49+
50+
deleteCity(id: number) {
51+
this.store.deleteOne(id);
52+
}
53+
}
Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,59 @@
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';
13+
import { CardListItemTemplateDirective } from '../../ui/card/card-list-item-template.directive';
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: `
1520
<app-card
1621
[list]="students()"
17-
[type]="cardType"
18-
customClass="bg-light-green" />
22+
customClass="bg-light-green"
23+
(onAddNewItem)="addNewStudent()">
24+
<img ngSrc="assets/img/student.webp" width="200" height="200" />
25+
26+
<ng-template appCardListItem let-item="item">
27+
<app-list-item
28+
[name]="item.firstName"
29+
[id]="item.id"
30+
(onDelete)="deleteStudent(item.id)"></app-list-item>
31+
</ng-template>
32+
</app-card>
1933
`,
20-
styles: [
21-
`
22-
::ng-deep .bg-light-green {
23-
background-color: rgba(0, 250, 0, 0.1);
24-
}
25-
`,
34+
imports: [
35+
CardComponent,
36+
CardListItemTemplateDirective,
37+
NgOptimizedImage,
38+
ListItemComponent,
2639
],
27-
imports: [CardComponent],
2840
changeDetection: ChangeDetectionStrategy.OnPush,
2941
})
3042
export class StudentCardComponent implements OnInit {
3143
private http = inject(FakeHttpService);
3244
private store = inject(StudentStore);
3345

3446
students = this.store.students;
35-
cardType = CardType.STUDENT;
3647

3748
ngOnInit(): void {
3849
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
3950
}
51+
52+
addNewStudent() {
53+
this.store.addOne(randStudent());
54+
}
55+
56+
deleteStudent(id: number) {
57+
this.store.deleteOne(id);
58+
}
4059
}
Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
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';
48
import { CardType } from '../../model/card.model';
9+
import { CardListItemTemplateDirective } from '../../ui/card/card-list-item-template.directive';
510
import { CardComponent } from '../../ui/card/card.component';
11+
import { ListItemComponent } from '../../ui/list-item/list-item.component';
612

713
@Component({
814
selector: 'app-teacher-card',
915
template: `
1016
<app-card
1117
[list]="teachers()"
12-
[type]="cardType"
13-
customClass="bg-light-red"></app-card>
18+
customClass="bg-light-red"
19+
(onAddNewItem)="addNewTeacher()">
20+
<img ngSrc="assets/img/teacher.png" width="200" height="200" />
21+
22+
<ng-template appCardListItem let-item="item">
23+
<app-list-item
24+
[name]="item.firstName"
25+
[id]="item.id"
26+
(onDelete)="deleteTeacher(item.id)"></app-list-item>
27+
</ng-template>
28+
</app-card>
1429
`,
15-
styles: [
16-
`
17-
::ng-deep .bg-light-red {
18-
background-color: rgba(250, 0, 0, 0.1);
19-
}
20-
`,
30+
imports: [
31+
CardComponent,
32+
CardListItemTemplateDirective,
33+
NgOptimizedImage,
34+
ListItemComponent,
2135
],
22-
imports: [CardComponent],
2336
})
2437
export class TeacherCardComponent implements OnInit {
2538
private http = inject(FakeHttpService);
@@ -31,4 +44,12 @@ export class TeacherCardComponent implements OnInit {
3144
ngOnInit(): void {
3245
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
3346
}
47+
48+
addNewTeacher() {
49+
this.store.addOne(randTeacher());
50+
}
51+
52+
deleteTeacher(id: number) {
53+
this.store.deleteOne(id);
54+
}
3455
}

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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Directive, TemplateRef, inject } from '@angular/core';
2+
import { City } from '../../model/city.model';
3+
import { Student } from '../../model/student.model';
4+
import { Teacher } from '../../model/teacher.model';
5+
6+
type CardItem = Student | City | Teacher;
7+
8+
@Directive({
9+
selector: 'ng-template[appCardListItem]',
10+
exportAs: 'appCardListItem',
11+
})
12+
export class CardListItemTemplateDirective {
13+
template: TemplateRef<{ $implicit: CardItem; item: CardItem }> =
14+
inject(TemplateRef);
15+
}
Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,21 @@
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 { NgTemplateOutlet } from '@angular/common';
2+
import { Component, contentChild, input, output } from '@angular/core';
3+
import { CardListItemTemplateDirective } from './card-list-item-template.directive';
84

95
@Component({
106
selector: 'app-card',
117
template: `
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-
}
21-
11+
<ng-content />
2212
<section>
2313
@for (item of list(); track item) {
24-
<app-list-item
25-
[name]="item.firstName"
26-
[id]="item.id"
27-
[type]="type()"></app-list-item>
14+
<ng-container
15+
*ngTemplateOutlet="
16+
listItemTemplate()?.template ?? null;
17+
context: { $implicit: item, item: item }
18+
"></ng-container>
2819
}
2920
</section>
3021
@@ -35,24 +26,32 @@ import { ListItemComponent } from '../list-item/list-item.component';
3526
</button>
3627
</div>
3728
`,
38-
imports: [ListItemComponent, NgOptimizedImage],
29+
styles: [
30+
`
31+
.bg-light-red {
32+
background-color: rgba(250, 0, 0, 0.1);
33+
}
34+
35+
.bg-light-green {
36+
background-color: rgba(0, 250, 0, 0.1);
37+
}
38+
39+
.bg-light-blue {
40+
background-color: rgba(0, 0, 250, 0.1);
41+
}
42+
`,
43+
],
44+
imports: [NgTemplateOutlet, CardListItemTemplateDirective],
3945
})
4046
export class CardComponent {
41-
private teacherStore = inject(TeacherStore);
42-
private studentStore = inject(StudentStore);
43-
4447
readonly list = input<any[] | null>(null);
45-
readonly type = input.required<CardType>();
4648
readonly customClass = input('');
49+
onAddNewItem = output<void>();
4750

48-
CardType = CardType;
51+
// Use contentChild to query for the directive
52+
listItemTemplate = contentChild(CardListItemTemplateDirective);
4953

5054
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-
}
55+
this.onAddNewItem.emit();
5756
}
5857
}
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
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';
9-
import { CardType } from '../../model/card.model';
107

118
@Component({
129
selector: 'app-list-item',
@@ -21,19 +18,11 @@ import { CardType } from '../../model/card.model';
2118
changeDetection: ChangeDetectionStrategy.OnPush,
2219
})
2320
export class ListItemComponent {
24-
private teacherStore = inject(TeacherStore);
25-
private studentStore = inject(StudentStore);
26-
2721
readonly id = input.required<number>();
2822
readonly name = input.required<string>();
29-
readonly type = input.required<CardType>();
23+
onDelete = output<number>();
3024

3125
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-
}
26+
this.onDelete.emit(id);
3827
}
3928
}

0 commit comments

Comments
 (0)