feat(challenge1): modernization

This commit is contained in:
thomas
2025-01-27 21:39:33 +01:00
parent 9c7a37013e
commit e58a789133
10 changed files with 83 additions and 95 deletions

View File

@@ -1,12 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-city-card',
template: 'TODO City',
imports: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CityCardComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}
export class CityCardComponent {}

View File

@@ -1,17 +1,21 @@
import { Component, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core';
import { FakeHttpService } 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';
@Component({
selector: 'app-student-card',
template: `
<app-card
[list]="students"
[list]="students()"
[type]="cardType"
customClass="bg-light-green"></app-card>
customClass="bg-light-green" />
`,
styles: [
`
@@ -21,19 +25,16 @@ import { CardComponent } from '../../ui/card/card.component';
`,
],
imports: [CardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StudentCardComponent implements OnInit {
students: Student[] = [];
cardType = CardType.STUDENT;
private http = inject(FakeHttpService);
private store = inject(StudentStore);
constructor(
private http: FakeHttpService,
private store: StudentStore,
) {}
students = this.store.students;
cardType = CardType.STUDENT;
ngOnInit(): void {
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
this.store.students$.subscribe((s) => (this.students = s));
}
}

View File

@@ -1,15 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { Component, inject, OnInit } from '@angular/core';
import { FakeHttpService } 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';
@Component({
selector: 'app-teacher-card',
template: `
<app-card
[list]="teachers"
[list]="teachers()"
[type]="cardType"
customClass="bg-light-red"></app-card>
`,
@@ -23,17 +22,13 @@ import { CardComponent } from '../../ui/card/card.component';
imports: [CardComponent],
})
export class TeacherCardComponent implements OnInit {
teachers: Teacher[] = [];
cardType = CardType.TEACHER;
private http = inject(FakeHttpService);
private store = inject(TeacherStore);
constructor(
private http: FakeHttpService,
private store: TeacherStore,
) {}
teachers = this.store.teachers;
cardType = CardType.TEACHER;
ngOnInit(): void {
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
this.store.teachers$.subscribe((t) => (this.teachers = t));
}
}

View File

@@ -1,23 +1,21 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Injectable, signal } from '@angular/core';
import { City } from '../model/city.model';
@Injectable({
providedIn: 'root',
})
export class CityStore {
private cities = new BehaviorSubject<City[]>([]);
cities$ = this.cities.asObservable();
private cities = signal<City[]>([]);
addAll(cities: City[]) {
this.cities.next(cities);
this.cities.set(cities);
}
addOne(student: City) {
this.cities.next([...this.cities.value, student]);
this.cities.set([...this.cities(), student]);
}
deleteOne(id: number) {
this.cities.next(this.cities.value.filter((s) => s.id !== id));
this.cities.set(this.cities().filter((s) => s.id !== id));
}
}

View File

@@ -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<Student[]>([]);
students$ = this.students.asObservable();
public students = signal<Student[]>([]);
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));
}
}

View File

@@ -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<Teacher[]>([]);
teachers$ = this.teachers.asObservable();
public teachers = signal<Teacher[]>([]);
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));
}
}

View File

@@ -1,5 +1,5 @@
import { NgFor, NgIf } from '@angular/common';
import { Component, Input } from '@angular/core';
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';
@@ -11,22 +11,21 @@ import { ListItemComponent } from '../list-item/list-item.component';
template: `
<div
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
[class]="customClass">
<img
*ngIf="type === CardType.TEACHER"
src="assets/img/teacher.png"
width="200px" />
<img
*ngIf="type === CardType.STUDENT"
src="assets/img/student.webp"
width="200px" />
[class]="customClass()">
@if (type() === CardType.TEACHER) {
<img ngSrc="assets/img/teacher.png" width="200" height="200" />
}
@if (type() === CardType.STUDENT) {
<img ngSrc="assets/img/student.webp" width="200" height="200" />
}
<section>
@for (item of list(); track item) {
<app-list-item
*ngFor="let item of list"
[name]="item.firstName"
[id]="item.id"
[type]="type"></app-list-item>
[type]="type()"></app-list-item>
}
</section>
<button
@@ -36,24 +35,23 @@ import { ListItemComponent } from '../list-item/list-item.component';
</button>
</div>
`,
imports: [NgIf, NgFor, ListItemComponent],
imports: [ListItemComponent, NgOptimizedImage],
})
export class CardComponent {
@Input() list: any[] | null = null;
@Input() type!: CardType;
@Input() customClass = '';
private teacherStore = inject(TeacherStore);
private studentStore = inject(StudentStore);
readonly list = input<any[] | null>(null);
readonly type = input.required<CardType>();
readonly customClass = input('');
CardType = CardType;
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
) {}
addNewItem() {
if (this.type === CardType.TEACHER) {
const type = this.type();
if (type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (this.type === CardType.STUDENT) {
} else if (type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
}
}

View File

@@ -1,4 +1,9 @@
import { Component, Input } from '@angular/core';
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';
@@ -7,28 +12,28 @@ import { CardType } from '../../model/card.model';
selector: 'app-list-item',
template: `
<div class="border-grey-300 flex justify-between border px-2 py-1">
{{ name }}
<button (click)="delete(id)">
{{ name() }}
<button (click)="delete(id())">
<img class="h-5" src="assets/svg/trash.svg" />
</button>
</div>
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ListItemComponent {
@Input() id!: number;
@Input() name!: string;
@Input() type!: CardType;
private teacherStore = inject(TeacherStore);
private studentStore = inject(StudentStore);
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
) {}
readonly id = input.required<number>();
readonly name = input.required<string>();
readonly type = input.required<CardType>();
delete(id: number) {
if (this.type === CardType.TEACHER) {
const type = this.type();
if (type === CardType.TEACHER) {
this.teacherStore.deleteOne(id);
} else if (this.type === CardType.STUDENT) {
} else if (type === CardType.STUDENT) {
this.studentStore.deleteOne(id);
}
}

View File

@@ -41,12 +41,11 @@ While the application works, the developer experience is far from being optimal.
## Constraints
- You <b>must</b> refactor the `CardComponent` and `ListItemComponent`.
- The `NgFor` directive must be declared and remain inside the `CardComponent`. You might be tempted to move it to the `ParentCardComponent` like `TeacherCardComponent`.
- `CardComponent` should not contain any `NgIf` or `NgSwitch`.
- The `@for` must be declared and remain inside the `CardComponent`. You might be tempted to move it to the `ParentCardComponent` like `TeacherCardComponent`.
- `CardComponent` should not contain any conditions.
- CSS: try to avoid using `::ng-deep`. Find a better way to handle CSS styling.
## Bonus Challenges
- Try to work with the new built-in control flow syntax for loops and conditionals (documentation [here](https://angular.dev/guide/templates/control-flow))
- Use the signal API to manage your components state (documentation [here](https://angular.dev/guide/signals))
- To reference the template, use a directive instead of magic strings ([What is wrong with magic strings?](https://softwareengineering.stackexchange.com/a/365344))

View File

@@ -37,12 +37,11 @@ Bien que l'application fonctionne, l'expérience développeur est loin d'être o
## Contraintes
- Vous <b>devez</b> refactoriser le `CardComponent` et le `ListItemComponent`.
- La directive `NgFor` doit être déclarée et rester à l'intérieur du `CardComponent`. Vous pourriez être tenté de la déplacer dans le `ParentCardComponent` comme `TeacherCardComponent`.
- Le composant `CardComponent` ne doit contenir aucun `NgIf` ni `NgSwitch`.
- La boucle `@for` doit être déclarée et rester à l'intérieur du `CardComponent`. Vous pourriez être tenté de la déplacer dans le `ParentCardComponent` comme `TeacherCardComponent`.
- Le composant `CardComponent` ne doit contenir aucune condition.
- CSS: essayez d'éviter d'utiliser `::ng-deep`. Trouvez un meilleur moyen de gérer le style CSS.
## Challenges Bonus
- Essayez de travailler avec la nouvelle syntaxe de contrôle de flux pour les boucles et les conditions (documentation [ici](https://angular.dev/guide/templates/control-flow))
- Utilisez l'API des signals pour gérer l'état de vos composants (documentation [ici](https://angular.dev/guide/signals))
- Pour référencer le template, utilisez une directive au lieu d'une magic string ([Qu'est-ce qui pose problème avec les magic string ?](https://softwareengineering.stackexchange.com/a/365344))