diff --git a/apps/angular/1-projection/src/app/app.component.ts b/apps/angular/1-projection/src/app/app.component.ts index a8b7ab73b..70712c3cb 100644 --- a/apps/angular/1-projection/src/app/app.component.ts +++ b/apps/angular/1-projection/src/app/app.component.ts @@ -1,3 +1,4 @@ +// app.component.ts import { ChangeDetectionStrategy, Component } from '@angular/core'; import { CityCardComponent } from './component/city-card/city-card.component'; import { StudentCardComponent } from './component/student-card/student-card.component'; diff --git a/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts b/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts index 8895c8c84..8981a81e7 100644 --- a/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts +++ b/apps/angular/1-projection/src/app/component/city-card/city-card.component.ts @@ -1,9 +1,60 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +// city-card.component.ts +import { + ChangeDetectionStrategy, + Component, + inject, + OnInit, +} from '@angular/core'; +import { CityStore } from '../../data-access/city.store'; +import { + FakeHttpService, + randomCity, +} from '../../data-access/fake-http.service'; +import { CardRowDirective } from '../../ui/card/card-row.directive'; +import { CardComponent } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-city-card', - template: 'TODO City', - imports: [], + standalone: true, + imports: [CardComponent, ListItemComponent, CardRowDirective], + template: ` + + + city + + + + + {{ city.name }} + + + + + + + `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CityCardComponent {} +export class CityCardComponent implements OnInit { + private http = inject(FakeHttpService); + private store = inject(CityStore); + + readonly cities = this.store.cities; + + ngOnInit(): void { + this.http.fetchCities$.subscribe((cities) => this.store.addAll(cities)); + } + + addCity(): void { + this.store.addOne(randomCity()); + } + + deleteCity(id: number): void { + this.store.deleteOne(id); + } +} diff --git a/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts b/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts index bdfa4abd4..72174eebc 100644 --- a/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts +++ b/apps/angular/1-projection/src/app/component/student-card/student-card.component.ts @@ -1,21 +1,46 @@ +// student-card.component.ts import { ChangeDetectionStrategy, Component, inject, OnInit, } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { + FakeHttpService, + randStudent, +} from '../../data-access/fake-http.service'; // Fix 2: Imported randStudent import { StudentStore } from '../../data-access/student.store'; -import { CardType } from '../../model/card.model'; +import { CardRowDirective } from '../../ui/card/card-row.directive'; // Added missing import import { CardComponent } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; // Added missing import @Component({ selector: 'app-student-card', template: ` - + + + + + student + + + + + {{ student.firstName }} + + + + + + `, styles: [ ` @@ -24,17 +49,24 @@ import { CardComponent } from '../../ui/card/card.component'; } `, ], - imports: [CardComponent], + imports: [CardComponent, ListItemComponent, CardRowDirective], // Added missing components/directives to imports changeDetection: ChangeDetectionStrategy.OnPush, }) export class StudentCardComponent implements OnInit { private http = inject(FakeHttpService); private store = inject(StudentStore); - students = this.store.students; - cardType = CardType.STUDENT; + readonly students = this.store.students; ngOnInit(): void { this.http.fetchStudents$.subscribe((s) => this.store.addAll(s)); } + + addStudent(): void { + this.store.addOne(randStudent()); + } + + deleteStudent(id: number): void { + this.store.deleteOne(id); + } } diff --git a/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts b/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts index 3e454ac19..5d87b6545 100644 --- a/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts +++ b/apps/angular/1-projection/src/app/component/teacher-card/teacher-card.component.ts @@ -1,40 +1,66 @@ +// teacher-card.component.ts import { ChangeDetectionStrategy, Component, inject, OnInit, } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { + FakeHttpService, + randTeacher, +} from '../../data-access/fake-http.service'; import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; +import { CardRowDirective } from '../../ui/card/card-row.directive'; import { CardComponent } from '../../ui/card/card.component'; - +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-teacher-card', + standalone: true, + imports: [CardComponent, ListItemComponent, CardRowDirective], template: ` - + + + teacher + + + + + {{ teacher.firstName }} + + + + + + `, - styles: [ - ` - ::ng-deep .bg-light-red { - background-color: rgba(250, 0, 0, 0.1); - } - `, - ], changeDetection: ChangeDetectionStrategy.Eager, - imports: [CardComponent], }) export class TeacherCardComponent implements OnInit { private http = inject(FakeHttpService); private store = inject(TeacherStore); - teachers = this.store.teachers; - cardType = CardType.TEACHER; + // teachers = this.store.teachers; + // cardType = CardType.TEACHER; + + readonly teachers = this.store.teachers; ngOnInit(): void { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); } + + addTeacher(): void { + this.store.addOne(randTeacher()); + } + + deleteTeacher(id: number): void { + this.store.deleteOne(id); + } } diff --git a/apps/angular/1-projection/src/app/data-access/city.store.ts b/apps/angular/1-projection/src/app/data-access/city.store.ts index a8b523569..14cd447b0 100644 --- a/apps/angular/1-projection/src/app/data-access/city.store.ts +++ b/apps/angular/1-projection/src/app/data-access/city.store.ts @@ -1,3 +1,4 @@ +// city.store.ts import { Injectable, signal } from '@angular/core'; import { City } from '../model/city.model'; @@ -5,17 +6,18 @@ import { City } from '../model/city.model'; providedIn: 'root', }) export class CityStore { - private cities = signal([]); + readonly #cities = signal([]); + readonly cities = this.#cities.asReadonly(); addAll(cities: City[]) { - this.cities.set(cities); + this.#cities.set(cities); } addOne(city: City) { - this.cities.set([...this.cities(), city]); + this.#cities.set([...this.#cities(), city]); } deleteOne(id: number) { - this.cities.set(this.cities().filter((s) => s.id !== id)); + this.#cities.set(this.#cities().filter((s) => s.id !== id)); } } diff --git a/apps/angular/1-projection/src/app/data-access/fake-http.service.ts b/apps/angular/1-projection/src/app/data-access/fake-http.service.ts index 82a8f1813..7a3ea0e79 100644 --- a/apps/angular/1-projection/src/app/data-access/fake-http.service.ts +++ b/apps/angular/1-projection/src/app/data-access/fake-http.service.ts @@ -1,3 +1,4 @@ +// fake-http.service.ts import { Injectable } from '@angular/core'; import { incrementalNumber, diff --git a/apps/angular/1-projection/src/app/ui/card/card-row.directive.ts b/apps/angular/1-projection/src/app/ui/card/card-row.directive.ts new file mode 100644 index 000000000..4c1b7f80f --- /dev/null +++ b/apps/angular/1-projection/src/app/ui/card/card-row.directive.ts @@ -0,0 +1,13 @@ +import { Directive, inject, TemplateRef } from '@angular/core'; + +export interface CardRowContext { + $implicit: T; +} + +@Directive({ + selector: 'ng-template[cardRow]', + standalone: true, +}) +export class CardRowDirective { + readonly templateRef = inject(TemplateRef>); +} diff --git a/apps/angular/1-projection/src/app/ui/card/card.component.ts b/apps/angular/1-projection/src/app/ui/card/card.component.ts index d395474e2..ee613c816 100644 --- a/apps/angular/1-projection/src/app/ui/card/card.component.ts +++ b/apps/angular/1-projection/src/app/ui/card/card.component.ts @@ -1,64 +1,49 @@ -import { NgOptimizedImage } from '@angular/common'; +// card.component.ts + +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, - inject, + contentChild, input, } from '@angular/core'; -import { randStudent, randTeacher } from '../../data-access/fake-http.service'; -import { StudentStore } from '../../data-access/student.store'; -import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; -import { ListItemComponent } from '../list-item/list-item.component'; +// import { randStudent, randTeacher } from '../../data-access/fake-http.service'; +// import { StudentStore } from '../../data-access/student.store'; +// import { TeacherStore } from '../../data-access/teacher.store'; +// import { CardType } from '../../model/card.model'; +// import { ListItemComponent } from '../list-item/list-item.component'; +import { CardRowDirective } from './card-row.directive'; @Component({ selector: 'app-card', + standalone: true, + imports: [NgTemplateOutlet], template: `
- @if (type() === CardType.TEACHER) { - - } - @if (type() === CardType.STUDENT) { - - } + + +
- @for (item of list(); track item) { - + @for (item of list(); track item.id) { + }
- + +
`, changeDetection: ChangeDetectionStrategy.Eager, - imports: [ListItemComponent, NgOptimizedImage], }) -export class CardComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); - - readonly list = input(null); - readonly type = input.required(); +export class CardComponent { + readonly list = input(null); readonly customClass = input(''); - CardType = CardType; - - addNewItem() { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.addOne(randTeacher()); - } else if (type === CardType.STUDENT) { - this.studentStore.addOne(randStudent()); - } - } + // Bonus: Query row template via Directive instead of magic string + readonly rowTemplate = contentChild(CardRowDirective); } diff --git a/apps/angular/1-projection/src/app/ui/card/city-card.component.ts b/apps/angular/1-projection/src/app/ui/card/city-card.component.ts new file mode 100644 index 000000000..eb439868b --- /dev/null +++ b/apps/angular/1-projection/src/app/ui/card/city-card.component.ts @@ -0,0 +1,36 @@ +// city-card.component.ts +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { randCity } from '@ngneat/falso/src/lib/city'; +import { CityStore } from '../../data-access/city.store'; +import { CardRowDirective } from '../card/card-row.directive'; +import { CardComponent } from '../card/card.component'; +import { ListItemComponent } from '../list-item/list-item.component'; + +@Component({ + selector: 'app-city-card', + standalone: true, + imports: [CardComponent, ListItemComponent, CardRowDirective], + template: ` + + city + + + + {{ city.name }} + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CityCardComponent { + protected cityStore = inject(CityStore); + protected cities = this.cityStore.cities; + protected randCity = randCity; +} diff --git a/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts b/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts index c04a29c05..036577794 100644 --- a/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts +++ b/apps/angular/1-projection/src/app/ui/list-item/list-item.component.ts @@ -1,19 +1,15 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - input, -} from '@angular/core'; -import { StudentStore } from '../../data-access/student.store'; -import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; +// list-item.component.ts + +import { ChangeDetectionStrategy, Component, output } from '@angular/core'; +// import { StudentStore } from '../../data-access/student.store'; +// import { TeacherStore } from '../../data-access/teacher.store'; @Component({ selector: 'app-list-item', template: `
- {{ name() }} -
@@ -21,19 +17,20 @@ import { CardType } from '../../model/card.model'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListItemComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); + // private teacherStore = inject(TeacherStore); + // private studentStore = inject(StudentStore); - readonly id = input.required(); - readonly name = input.required(); - readonly type = input.required(); + // readonly id = input.required(); + // readonly name = input.required(); + // readonly type = input.required(); - delete(id: number) { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.deleteOne(id); - } else if (type === CardType.STUDENT) { - this.studentStore.deleteOne(id); - } - } + // delete(id: number) { + // const type = this.type(); + // if (type === CardType.TEACHER) { + // this.teacherStore.deleteOne(id); + // } else if (type === CardType.STUDENT) { + // this.studentStore.deleteOne(id); + // } + // } + readonly deleted = output(); } diff --git a/apps/angular/1-projection/src/main.ts b/apps/angular/1-projection/src/main.ts index 63574fdcf..c0dca34cf 100644 --- a/apps/angular/1-projection/src/main.ts +++ b/apps/angular/1-projection/src/main.ts @@ -1,3 +1,5 @@ +// main.ts + import { provideZoneChangeDetection } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; diff --git a/apps/angular/1-projection/src/polyfills.ts b/apps/angular/1-projection/src/polyfills.ts index e4555ed11..eb11db65b 100644 --- a/apps/angular/1-projection/src/polyfills.ts +++ b/apps/angular/1-projection/src/polyfills.ts @@ -1,4 +1,5 @@ /** + * polyfills.ts * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * diff --git a/apps/angular/1-projection/src/styles.css b/apps/angular/1-projection/src/styles.css index 3d552a61f..429e9aabc 100644 --- a/apps/angular/1-projection/src/styles.css +++ b/apps/angular/1-projection/src/styles.css @@ -1,2 +1,3 @@ +/* styles.css */ @import "tailwindcss"; diff --git a/apps/angular/1-projection/src/test-setup.ts b/apps/angular/1-projection/src/test-setup.ts index 58c511e08..b2e4ad5f3 100644 --- a/apps/angular/1-projection/src/test-setup.ts +++ b/apps/angular/1-projection/src/test-setup.ts @@ -1,3 +1,4 @@ +// test-setup.ts import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; setupZoneTestEnv();