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..1655473e5 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,63 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { NgOptimizedImage } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { CityStore } from '../../data-access/city.store'; +import { + FakeHttpService, + randomCity, +} from '../../data-access/fake-http.service'; +import { + CardAction, + CardActionType, + CardComponent, +} from '../../ui/card/card.component'; +import { CardListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-city-card', - template: 'TODO City', - imports: [], + template: ` + + + + + + + + `, + imports: [ + CardComponent, + CardListItemTemplateDirective, + ListItemComponent, + NgOptimizedImage, + ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CityCardComponent {} +export class CityCardComponent { + private http = inject(FakeHttpService); + private store = inject(CityStore); + + cities = this.store.cities; + + ngOnInit(): void { + this.http.fetchCities$.subscribe((s) => this.store.addAll(s)); + } + + cardActions(action: CardAction) { + switch (action.type) { + case CardActionType.ADD: + this.store.addOne(randomCity()); + break; + case CardActionType.DELETE: + this.store.deleteOne(action.payload.id); + break; + default: + console.log('Unknown'); + } + } +} 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..826e87a54 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,30 +1,55 @@ +import { NgOptimizedImage } from '@angular/common'; import { ChangeDetectionStrategy, Component, inject, OnInit, + ViewEncapsulation, } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { + FakeHttpService, + randStudent, +} from '../../data-access/fake-http.service'; import { StudentStore } from '../../data-access/student.store'; -import { CardType } from '../../model/card.model'; -import { CardComponent } from '../../ui/card/card.component'; +import { + CardAction, + CardActionType, + CardComponent, +} from '../../ui/card/card.component'; +import { CardListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-student-card', template: ` + customClass="bg-light-green" + (actions)="cardActions($event)"> + + + + + + `, styles: [ ` - ::ng-deep .bg-light-green { + .bg-light-green { background-color: rgba(0, 250, 0, 0.1); } `, ], - imports: [CardComponent], + encapsulation: ViewEncapsulation.None, + imports: [ + CardComponent, + CardListItemTemplateDirective, + ListItemComponent, + NgOptimizedImage, + ], changeDetection: ChangeDetectionStrategy.OnPush, }) export class StudentCardComponent implements OnInit { @@ -32,9 +57,21 @@ export class StudentCardComponent implements OnInit { private store = inject(StudentStore); students = this.store.students; - cardType = CardType.STUDENT; ngOnInit(): void { this.http.fetchStudents$.subscribe((s) => this.store.addAll(s)); } + + cardActions(action: CardAction) { + switch (action.type) { + case CardActionType.ADD: + this.store.addOne(randStudent()); + break; + case CardActionType.DELETE: + this.store.deleteOne(action.payload.id); + break; + default: + console.log('Unknown'); + } + } } 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 adf0ad3c1..f8b3077bf 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,34 +1,70 @@ -import { Component, inject, OnInit } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { NgOptimizedImage } from '@angular/common'; +import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { + FakeHttpService, + randTeacher, +} from '../../data-access/fake-http.service'; import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; -import { CardComponent } from '../../ui/card/card.component'; +import { + CardAction, + CardActionType, + CardComponent, +} from '../../ui/card/card.component'; +import { CardListItemTemplateDirective } from '../../ui/list-item/list-item-template.directive'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-teacher-card', template: ` + customClass="bg-light-red" + (actions)="cardActions($event)"> + + + + + + `, styles: [ ` - ::ng-deep .bg-light-red { + .bg-light-red { background-color: rgba(250, 0, 0, 0.1); } `, ], - imports: [CardComponent], + encapsulation: ViewEncapsulation.None, + imports: [ + CardComponent, + CardListItemTemplateDirective, + ListItemComponent, + NgOptimizedImage, + ], }) export class TeacherCardComponent implements OnInit { private http = inject(FakeHttpService); private store = inject(TeacherStore); teachers = this.store.teachers; - cardType = CardType.TEACHER; ngOnInit(): void { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); } + + cardActions(action: CardAction) { + switch (action.type) { + case CardActionType.ADD: + this.store.addOne(randTeacher()); + break; + case CardActionType.DELETE: + this.store.deleteOne(action.payload.id); + break; + default: + console.log('Unhandled action: ' + action); + } + } } 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..9fbcb346b 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 @@ -5,7 +5,7 @@ import { City } from '../model/city.model'; providedIn: 'root', }) export class CityStore { - private cities = signal([]); + public cities = signal([]); addAll(cities: City[]) { this.cities.set(cities); 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 166d9c49a..756bc2ed6 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,10 +1,15 @@ -import { NgOptimizedImage } from '@angular/common'; -import { Component, inject, 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 { NgTemplateOutlet } from '@angular/common'; +import { Component, contentChild, input, output } from '@angular/core'; +import { CardListItemTemplateDirective } from '../list-item/list-item-template.directive'; + +export enum CardActionType { + ADD = 'add', + DELETE = 'delete', +} + +export type CardAction = + | { type: CardActionType.ADD } + | { type: CardActionType.DELETE; payload: { id: number } }; @Component({ selector: 'app-card', @@ -12,19 +17,13 @@ import { ListItemComponent } from '../list-item/list-item.component';
- @if (type() === CardType.TEACHER) { - - } - @if (type() === CardType.STUDENT) { - - } +
@for (item of list(); track item) { - + }
@@ -35,24 +34,28 @@ import { ListItemComponent } from '../list-item/list-item.component';
`, - imports: [ListItemComponent, NgOptimizedImage], + imports: [NgTemplateOutlet, CardListItemTemplateDirective], }) export class CardComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); - readonly list = input(null); - readonly type = input.required(); readonly customClass = input(''); - CardType = CardType; + actions = output(); + + listItemTemplate = contentChild(CardListItemTemplateDirective); + + getItemContext(item: any) { + return { + $implicit: item, + onDeleteAction: (id: number) => this.deleteItem(id), + }; + } addNewItem() { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.addOne(randTeacher()); - } else if (type === CardType.STUDENT) { - this.studentStore.addOne(randStudent()); - } + this.actions.emit({ type: CardActionType.ADD }); + } + + deleteItem(id: number) { + this.actions.emit({ type: CardActionType.DELETE, payload: { id } }); } } diff --git a/apps/angular/1-projection/src/app/ui/list-item/list-item-template.directive.ts b/apps/angular/1-projection/src/app/ui/list-item/list-item-template.directive.ts new file mode 100644 index 000000000..9f502913b --- /dev/null +++ b/apps/angular/1-projection/src/app/ui/list-item/list-item-template.directive.ts @@ -0,0 +1,12 @@ +import { Directive, inject, TemplateRef } from '@angular/core'; + +@Directive({ + selector: 'ng-template[cardListItem]', + exportAs: 'cardListItem', +}) +export class CardListItemTemplateDirective { + template: TemplateRef<{ + $implicit: any; + onDeleteAction: (id: number) => void; + }> = inject(TemplateRef); +} 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 b36c45545..f860529ca 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,12 +1,9 @@ import { ChangeDetectionStrategy, Component, - inject, input, + output, } from '@angular/core'; -import { StudentStore } from '../../data-access/student.store'; -import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; @Component({ selector: 'app-list-item', @@ -21,19 +18,12 @@ import { CardType } from '../../model/card.model'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListItemComponent { - private teacherStore = inject(TeacherStore); - private studentStore = inject(StudentStore); - readonly id = input.required(); readonly name = input.required(); - readonly type = input.required(); + + deleteEvent = output(); delete(id: number) { - const type = this.type(); - if (type === CardType.TEACHER) { - this.teacherStore.deleteOne(id); - } else if (type === CardType.STUDENT) { - this.studentStore.deleteOne(id); - } + this.deleteEvent.emit(id); } }