From 48012bc381c86b4c23b55ffd607f944be1e17039 Mon Sep 17 00:00:00 2001 From: thomas laforge Date: Sat, 5 Nov 2022 21:39:08 +0100 Subject: [PATCH 1/3] feat(Answer:1): solution projection --- .../student-card/student-card.component.ts | 50 ++++++----- .../teacher-card/teacher-card.component.ts | 50 ++++++----- .../projection/src/app/model/card.model.ts | 5 -- .../src/app/ui/card/card.component.ts | 83 ++++++++----------- .../app/ui/list-item/list-item.component.ts | 34 +++----- 5 files changed, 106 insertions(+), 116 deletions(-) diff --git a/apps/angular/projection/src/app/component/student-card/student-card.component.ts b/apps/angular/projection/src/app/component/student-card/student-card.component.ts index 441cda189..62850a395 100644 --- a/apps/angular/projection/src/app/component/student-card/student-card.component.ts +++ b/apps/angular/projection/src/app/component/student-card/student-card.component.ts @@ -1,40 +1,52 @@ -import { Component, OnInit } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { AsyncPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { + FakeHttpService, + randStudent, +} from '../../data-access/fake-http.service'; import { StudentStore } from '../../data-access/student.store'; -import { CardType } from '../../model/card.model'; -import { Student } from '../../model/student.model'; import { CardComponent } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-student-card', - template: ` - - `, + template: ` + + + + {{ student.firstname }} + + + `, standalone: true, styles: [ ` - ::ng-deep .bg-light-green { + .bg-light-green { background-color: rgba(0, 250, 0, 0.1); } `, ], - imports: [CardComponent], + imports: [CardComponent, ListItemComponent, AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class StudentCardComponent implements OnInit { - students: Student[] = []; - cardType = CardType.STUDENT; + students$ = this.store.students$; - constructor( - private http: FakeHttpService, - private store: StudentStore, - ) {} + constructor(private http: FakeHttpService, private store: StudentStore) {} ngOnInit(): void { this.http.fetchStudents$.subscribe((s) => this.store.addAll(s)); + } + + addStudent() { + this.store.addOne(randStudent()); + } - this.store.students$.subscribe((s) => (this.students = s)); + deleteStudent(id: number) { + this.store.deleteOne(id); } } diff --git a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts index 995cb7c2f..8f43b666d 100644 --- a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts +++ b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts @@ -1,40 +1,52 @@ -import { Component, OnInit } from '@angular/core'; -import { FakeHttpService } from '../../data-access/fake-http.service'; +import { AsyncPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, OnInit } 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 { Teacher } from '../../model/teacher.model'; import { CardComponent } from '../../ui/card/card.component'; +import { ListItemComponent } from '../../ui/list-item/list-item.component'; @Component({ selector: 'app-teacher-card', - template: ` - - `, + template: ` + + + + {{ teacher.firstname }} + + + `, styles: [ ` - ::ng-deep .bg-light-red { + .bg-light-red { background-color: rgba(250, 0, 0, 0.1); } `, ], standalone: true, - imports: [CardComponent], + imports: [CardComponent, ListItemComponent, AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TeacherCardComponent implements OnInit { - teachers: Teacher[] = []; - cardType = CardType.TEACHER; + teachers$ = this.store.teachers$; - constructor( - private http: FakeHttpService, - private store: TeacherStore, - ) {} + constructor(private http: FakeHttpService, private store: TeacherStore) {} ngOnInit(): void { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); + } + + addTeacher() { + this.store.addOne(randTeacher()); + } - this.store.teachers$.subscribe((t) => (this.teachers = t)); + deleteTeacher(id: number) { + this.store.deleteOne(id); } } diff --git a/apps/angular/projection/src/app/model/card.model.ts b/apps/angular/projection/src/app/model/card.model.ts index 740cd2ae4..e69de29bb 100644 --- a/apps/angular/projection/src/app/model/card.model.ts +++ b/apps/angular/projection/src/app/model/card.model.ts @@ -1,5 +0,0 @@ -export enum CardType { - TEACHER, - STUDENT, - CITY, -} diff --git a/apps/angular/projection/src/app/ui/card/card.component.ts b/apps/angular/projection/src/app/ui/card/card.component.ts index f06c9ae00..be20707e6 100644 --- a/apps/angular/projection/src/app/ui/card/card.component.ts +++ b/apps/angular/projection/src/app/ui/card/card.component.ts @@ -1,61 +1,44 @@ -import { NgFor, NgIf } from '@angular/common'; -import { Component, 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 { NgFor, NgIf, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + ContentChild, + EventEmitter, + Input, + Output, + TemplateRef, +} from '@angular/core'; @Component({ selector: 'app-card', template: ` -
- - + -
- -
+
+ + + +
- -
+ `, standalone: true, - imports: [NgIf, NgFor, ListItemComponent], + imports: [NgIf, NgFor, NgTemplateOutlet], + host: { + class: 'border-2 border-black rounded-md p-4 w-fit flex flex-col gap-3', + }, + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CardComponent { - @Input() list: any[] | null = null; - @Input() type!: CardType; - @Input() customClass = ''; +export class CardComponent { + @Input() list: T[] | null = null; + @Output() add = new EventEmitter(); - CardType = CardType; - - constructor( - private teacherStore: TeacherStore, - private studentStore: StudentStore, - ) {} - - addNewItem() { - if (this.type === CardType.TEACHER) { - this.teacherStore.addOne(randTeacher()); - } else if (this.type === CardType.STUDENT) { - this.studentStore.addOne(randStudent()); - } - } + @ContentChild('rowRef', { read: TemplateRef }) + rowTemplate!: TemplateRef<{ $implicit: T }>; } diff --git a/apps/angular/projection/src/app/ui/list-item/list-item.component.ts b/apps/angular/projection/src/app/ui/list-item/list-item.component.ts index c0f9cff7f..862df5948 100644 --- a/apps/angular/projection/src/app/ui/list-item/list-item.component.ts +++ b/apps/angular/projection/src/app/ui/list-item/list-item.component.ts @@ -1,35 +1,23 @@ -import { Component, Input } from '@angular/core'; -import { StudentStore } from '../../data-access/student.store'; -import { TeacherStore } from '../../data-access/teacher.store'; -import { CardType } from '../../model/card.model'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Output, +} from '@angular/core'; @Component({ selector: 'app-list-item', template: ` -
- {{ name }} -
`, standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListItemComponent { - @Input() id!: number; - @Input() name!: string; - @Input() type!: CardType; - - constructor( - private teacherStore: TeacherStore, - private studentStore: StudentStore, - ) {} - - delete(id: number) { - if (this.type === CardType.TEACHER) { - this.teacherStore.deleteOne(id); - } else if (this.type === CardType.STUDENT) { - this.studentStore.deleteOne(id); - } - } + @Output() delete = new EventEmitter(); } From cfdc8f0ba13d5e55b0498551a4116a41718c210f Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 14 Apr 2024 22:18:12 +0200 Subject: [PATCH 2/3] feat: update solution --- .../teacher-card/teacher-card.component.ts | 32 ++++++++++--------- .../src/app/data-access/teacher.store.ts | 12 +++---- .../src/app/ui/card/card-row.directive.ts | 4 +++ .../src/app/ui/card/card.component.ts | 31 +++++++++--------- .../app/ui/list-item/list-item.component.ts | 15 +++------ 5 files changed, 46 insertions(+), 48 deletions(-) create mode 100644 apps/angular/projection/src/app/ui/card/card-row.directive.ts diff --git a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts index 8f43b666d..a04f17453 100644 --- a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts +++ b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts @@ -5,23 +5,22 @@ import { randTeacher, } from '../../data-access/fake-http.service'; import { TeacherStore } from '../../data-access/teacher.store'; +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', - template: ` - - - - {{ teacher.firstname }} - - - `, + template: ` + + + + + {{ teacher.firstname }} + + + + `, styles: [ ` .bg-light-red { @@ -30,13 +29,16 @@ import { ListItemComponent } from '../../ui/list-item/list-item.component'; `, ], standalone: true, - imports: [CardComponent, ListItemComponent, AsyncPipe], + imports: [ListItemComponent, AsyncPipe, CardRowDirective, CardComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) export class TeacherCardComponent implements OnInit { - teachers$ = this.store.teachers$; + teachers = this.store.teachers; - constructor(private http: FakeHttpService, private store: TeacherStore) {} + constructor( + private http: FakeHttpService, + private store: TeacherStore, + ) {} ngOnInit(): void { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); diff --git a/apps/angular/projection/src/app/data-access/teacher.store.ts b/apps/angular/projection/src/app/data-access/teacher.store.ts index 93f68c4b1..c30b6e0ec 100644 --- a/apps/angular/projection/src/app/data-access/teacher.store.ts +++ b/apps/angular/projection/src/app/data-access/teacher.store.ts @@ -1,23 +1,21 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { Injectable, signal } from '@angular/core'; import { Teacher } from '../model/teacher.model'; @Injectable({ providedIn: 'root', }) export class TeacherStore { - private teachers = new BehaviorSubject([]); - teachers$ = this.teachers.asObservable(); + teachers = signal([]); addAll(teachers: Teacher[]) { - this.teachers.next(teachers); + this.teachers.set(teachers); } addOne(teacher: Teacher) { - this.teachers.next([...this.teachers.value, teacher]); + this.teachers.set([...this.teachers(), teacher]); } deleteOne(id: number) { - this.teachers.next(this.teachers.value.filter((t) => t.id !== id)); + this.teachers.set(this.teachers().filter((t) => t.id !== id)); } } diff --git a/apps/angular/projection/src/app/ui/card/card-row.directive.ts b/apps/angular/projection/src/app/ui/card/card-row.directive.ts new file mode 100644 index 000000000..551cc0406 --- /dev/null +++ b/apps/angular/projection/src/app/ui/card/card-row.directive.ts @@ -0,0 +1,4 @@ +import { Directive } from '@angular/core'; + +@Directive({ selector: 'ng-template[cardRow]', standalone: true }) +export class CardRowDirective {} diff --git a/apps/angular/projection/src/app/ui/card/card.component.ts b/apps/angular/projection/src/app/ui/card/card.component.ts index be20707e6..499ef670e 100644 --- a/apps/angular/projection/src/app/ui/card/card.component.ts +++ b/apps/angular/projection/src/app/ui/card/card.component.ts @@ -1,44 +1,43 @@ -import { NgFor, NgIf, NgTemplateOutlet } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, - ContentChild, - EventEmitter, - Input, - Output, + contentChild, + input, + output, TemplateRef, } from '@angular/core'; +import { CardRowDirective } from './card-row.directive'; @Component({ selector: 'app-card', template: ` - +
- + @for (item of items(); track item.id) { - + }
`, standalone: true, - imports: [NgIf, NgFor, NgTemplateOutlet], + imports: [NgTemplateOutlet], host: { class: 'border-2 border-black rounded-md p-4 w-fit flex flex-col gap-3', }, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CardComponent { - @Input() list: T[] | null = null; - @Output() add = new EventEmitter(); +export class CardComponent { + items = input.required(); + add = output(); - @ContentChild('rowRef', { read: TemplateRef }) - rowTemplate!: TemplateRef<{ $implicit: T }>; + rowTemplate = contentChild.required(CardRowDirective, { read: TemplateRef }); } diff --git a/apps/angular/projection/src/app/ui/list-item/list-item.component.ts b/apps/angular/projection/src/app/ui/list-item/list-item.component.ts index 862df5948..45b1f7a78 100644 --- a/apps/angular/projection/src/app/ui/list-item/list-item.component.ts +++ b/apps/angular/projection/src/app/ui/list-item/list-item.component.ts @@ -1,16 +1,11 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, output } from '@angular/core'; @Component({ selector: 'app-list-item', template: ` -
- -
@@ -19,5 +14,5 @@ import { changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListItemComponent { - @Output() delete = new EventEmitter(); + delete = output(); } From 5f5140fb0b89faac4282402e3968cf24dcf837a2 Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 14 Apr 2024 22:39:40 +0200 Subject: [PATCH 3/3] feat: update projection solution --- .../student-card/student-card.component.ts | 36 +++++++++---------- .../teacher-card/teacher-card.component.ts | 18 +++++----- .../src/app/data-access/student.store.ts | 12 +++---- .../src/app/ui/card/card-row.directive.ts | 17 +++++++-- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/apps/angular/projection/src/app/component/student-card/student-card.component.ts b/apps/angular/projection/src/app/component/student-card/student-card.component.ts index 62850a395..569e6d6af 100644 --- a/apps/angular/projection/src/app/component/student-card/student-card.component.ts +++ b/apps/angular/projection/src/app/component/student-card/student-card.component.ts @@ -1,27 +1,26 @@ import { AsyncPipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { FakeHttpService, randStudent, } from '../../data-access/fake-http.service'; import { StudentStore } from '../../data-access/student.store'; +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-student-card', - template: ` - - - - {{ student.firstname }} - - - `, + template: ` + + + + + {{ student.firstName }} + + + + `, standalone: true, styles: [ ` @@ -30,15 +29,16 @@ import { ListItemComponent } from '../../ui/list-item/list-item.component'; } `, ], - imports: [CardComponent, ListItemComponent, AsyncPipe], + imports: [CardComponent, ListItemComponent, AsyncPipe, CardRowDirective], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class StudentCardComponent implements OnInit { - students$ = this.store.students$; +export class StudentCardComponent { + private http = inject(FakeHttpService); + private store = inject(StudentStore); - constructor(private http: FakeHttpService, private store: StudentStore) {} + students = this.store.students; - ngOnInit(): void { + constructor() { this.http.fetchStudents$.subscribe((s) => this.store.addAll(s)); } diff --git a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts index a04f17453..ed18d3fd2 100644 --- a/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts +++ b/apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { FakeHttpService, randTeacher, @@ -14,9 +14,9 @@ import { ListItemComponent } from '../../ui/list-item/list-item.component'; template: ` - + - {{ teacher.firstname }} + {{ teacher.firstName }} @@ -32,15 +32,13 @@ import { ListItemComponent } from '../../ui/list-item/list-item.component'; imports: [ListItemComponent, AsyncPipe, CardRowDirective, CardComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TeacherCardComponent implements OnInit { - teachers = this.store.teachers; +export class TeacherCardComponent { + private http = inject(FakeHttpService); + private store = inject(TeacherStore); - constructor( - private http: FakeHttpService, - private store: TeacherStore, - ) {} + teachers = this.store.teachers; - ngOnInit(): void { + constructor() { this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t)); } diff --git a/apps/angular/projection/src/app/data-access/student.store.ts b/apps/angular/projection/src/app/data-access/student.store.ts index 7918118c3..47e748162 100644 --- a/apps/angular/projection/src/app/data-access/student.store.ts +++ b/apps/angular/projection/src/app/data-access/student.store.ts @@ -1,23 +1,21 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { Injectable, signal } from '@angular/core'; import { Student } from '../model/student.model'; @Injectable({ providedIn: 'root', }) export class StudentStore { - private students = new BehaviorSubject([]); - students$ = this.students.asObservable(); + students = signal([]); addAll(students: Student[]) { - this.students.next(students); + this.students.set(students); } addOne(student: Student) { - this.students.next([...this.students.value, student]); + this.students.set([...this.students(), student]); } deleteOne(id: number) { - this.students.next(this.students.value.filter((s) => s.id !== id)); + this.students.set(this.students().filter((s) => s.id !== id)); } } diff --git a/apps/angular/projection/src/app/ui/card/card-row.directive.ts b/apps/angular/projection/src/app/ui/card/card-row.directive.ts index 551cc0406..ce3d637cb 100644 --- a/apps/angular/projection/src/app/ui/card/card-row.directive.ts +++ b/apps/angular/projection/src/app/ui/card/card-row.directive.ts @@ -1,4 +1,17 @@ -import { Directive } from '@angular/core'; +import { Directive, input } from '@angular/core'; + +interface CardRowContext { + $implicit: T; +} @Directive({ selector: 'ng-template[cardRow]', standalone: true }) -export class CardRowDirective {} +export class CardRowDirective { + cardRow = input.required(); + + static ngTemplateContextGuard( + dir: CardRowDirective, + ctx: unknown, + ): ctx is CardRowContext { + return true; + } +}