mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 04:43:03 -05:00
feat(challenge1): modernization
This commit is contained in:
@@ -1,12 +1,9 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-city-card',
|
selector: 'app-city-card',
|
||||||
template: 'TODO City',
|
template: 'TODO City',
|
||||||
imports: [],
|
imports: [],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CityCardComponent implements OnInit {
|
export class CityCardComponent {}
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
ngOnInit(): void {}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 { FakeHttpService } from '../../data-access/fake-http.service';
|
||||||
import { StudentStore } from '../../data-access/student.store';
|
import { StudentStore } from '../../data-access/student.store';
|
||||||
import { CardType } from '../../model/card.model';
|
import { CardType } from '../../model/card.model';
|
||||||
import { Student } from '../../model/student.model';
|
|
||||||
import { CardComponent } from '../../ui/card/card.component';
|
import { CardComponent } from '../../ui/card/card.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-student-card',
|
selector: 'app-student-card',
|
||||||
template: `
|
template: `
|
||||||
<app-card
|
<app-card
|
||||||
[list]="students"
|
[list]="students()"
|
||||||
[type]="cardType"
|
[type]="cardType"
|
||||||
customClass="bg-light-green"></app-card>
|
customClass="bg-light-green" />
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
@@ -21,19 +25,16 @@ import { CardComponent } from '../../ui/card/card.component';
|
|||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
imports: [CardComponent],
|
imports: [CardComponent],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class StudentCardComponent implements OnInit {
|
export class StudentCardComponent implements OnInit {
|
||||||
students: Student[] = [];
|
private http = inject(FakeHttpService);
|
||||||
cardType = CardType.STUDENT;
|
private store = inject(StudentStore);
|
||||||
|
|
||||||
constructor(
|
students = this.store.students;
|
||||||
private http: FakeHttpService,
|
cardType = CardType.STUDENT;
|
||||||
private store: StudentStore,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
|
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
|
||||||
|
|
||||||
this.store.students$.subscribe((s) => (this.students = s));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { FakeHttpService } from '../../data-access/fake-http.service';
|
||||||
import { TeacherStore } from '../../data-access/teacher.store';
|
import { TeacherStore } from '../../data-access/teacher.store';
|
||||||
import { CardType } from '../../model/card.model';
|
import { CardType } from '../../model/card.model';
|
||||||
import { Teacher } from '../../model/teacher.model';
|
|
||||||
import { CardComponent } from '../../ui/card/card.component';
|
import { CardComponent } from '../../ui/card/card.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-teacher-card',
|
selector: 'app-teacher-card',
|
||||||
template: `
|
template: `
|
||||||
<app-card
|
<app-card
|
||||||
[list]="teachers"
|
[list]="teachers()"
|
||||||
[type]="cardType"
|
[type]="cardType"
|
||||||
customClass="bg-light-red"></app-card>
|
customClass="bg-light-red"></app-card>
|
||||||
`,
|
`,
|
||||||
@@ -23,17 +22,13 @@ import { CardComponent } from '../../ui/card/card.component';
|
|||||||
imports: [CardComponent],
|
imports: [CardComponent],
|
||||||
})
|
})
|
||||||
export class TeacherCardComponent implements OnInit {
|
export class TeacherCardComponent implements OnInit {
|
||||||
teachers: Teacher[] = [];
|
private http = inject(FakeHttpService);
|
||||||
cardType = CardType.TEACHER;
|
private store = inject(TeacherStore);
|
||||||
|
|
||||||
constructor(
|
teachers = this.store.teachers;
|
||||||
private http: FakeHttpService,
|
cardType = CardType.TEACHER;
|
||||||
private store: TeacherStore,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
|
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
|
||||||
|
|
||||||
this.store.teachers$.subscribe((t) => (this.teachers = t));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, signal } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { City } from '../model/city.model';
|
import { City } from '../model/city.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class CityStore {
|
export class CityStore {
|
||||||
private cities = new BehaviorSubject<City[]>([]);
|
private cities = signal<City[]>([]);
|
||||||
cities$ = this.cities.asObservable();
|
|
||||||
|
|
||||||
addAll(cities: City[]) {
|
addAll(cities: City[]) {
|
||||||
this.cities.next(cities);
|
this.cities.set(cities);
|
||||||
}
|
}
|
||||||
|
|
||||||
addOne(student: City) {
|
addOne(student: City) {
|
||||||
this.cities.next([...this.cities.value, student]);
|
this.cities.set([...this.cities(), student]);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: number) {
|
deleteOne(id: number) {
|
||||||
this.cities.next(this.cities.value.filter((s) => s.id !== id));
|
this.cities.set(this.cities().filter((s) => s.id !== id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, signal } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { Student } from '../model/student.model';
|
import { Student } from '../model/student.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class StudentStore {
|
export class StudentStore {
|
||||||
private students = new BehaviorSubject<Student[]>([]);
|
public students = signal<Student[]>([]);
|
||||||
students$ = this.students.asObservable();
|
|
||||||
|
|
||||||
addAll(students: Student[]) {
|
addAll(students: Student[]) {
|
||||||
this.students.next(students);
|
this.students.set(students);
|
||||||
}
|
}
|
||||||
|
|
||||||
addOne(student: Student) {
|
addOne(student: Student) {
|
||||||
this.students.next([...this.students.value, student]);
|
this.students.set([...this.students(), student]);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: number) {
|
deleteOne(id: number) {
|
||||||
this.students.next(this.students.value.filter((s) => s.id !== id));
|
this.students.set(this.students().filter((s) => s.id !== id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, signal } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { Teacher } from '../model/teacher.model';
|
import { Teacher } from '../model/teacher.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class TeacherStore {
|
export class TeacherStore {
|
||||||
private teachers = new BehaviorSubject<Teacher[]>([]);
|
public teachers = signal<Teacher[]>([]);
|
||||||
teachers$ = this.teachers.asObservable();
|
|
||||||
|
|
||||||
addAll(teachers: Teacher[]) {
|
addAll(teachers: Teacher[]) {
|
||||||
this.teachers.next(teachers);
|
this.teachers.set(teachers);
|
||||||
}
|
}
|
||||||
|
|
||||||
addOne(teacher: Teacher) {
|
addOne(teacher: Teacher) {
|
||||||
this.teachers.next([...this.teachers.value, teacher]);
|
this.teachers.set([...this.teachers(), teacher]);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: number) {
|
deleteOne(id: number) {
|
||||||
this.teachers.next(this.teachers.value.filter((t) => t.id !== id));
|
this.teachers.set(this.teachers().filter((t) => t.id !== id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NgFor, NgIf } from '@angular/common';
|
import { NgOptimizedImage } from '@angular/common';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, inject, input } from '@angular/core';
|
||||||
import { randStudent, randTeacher } from '../../data-access/fake-http.service';
|
import { randStudent, randTeacher } from '../../data-access/fake-http.service';
|
||||||
import { StudentStore } from '../../data-access/student.store';
|
import { StudentStore } from '../../data-access/student.store';
|
||||||
import { TeacherStore } from '../../data-access/teacher.store';
|
import { TeacherStore } from '../../data-access/teacher.store';
|
||||||
@@ -11,22 +11,21 @@ import { ListItemComponent } from '../list-item/list-item.component';
|
|||||||
template: `
|
template: `
|
||||||
<div
|
<div
|
||||||
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
|
class="flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4"
|
||||||
[class]="customClass">
|
[class]="customClass()">
|
||||||
<img
|
@if (type() === CardType.TEACHER) {
|
||||||
*ngIf="type === CardType.TEACHER"
|
<img ngSrc="assets/img/teacher.png" width="200" height="200" />
|
||||||
src="assets/img/teacher.png"
|
}
|
||||||
width="200px" />
|
@if (type() === CardType.STUDENT) {
|
||||||
<img
|
<img ngSrc="assets/img/student.webp" width="200" height="200" />
|
||||||
*ngIf="type === CardType.STUDENT"
|
}
|
||||||
src="assets/img/student.webp"
|
|
||||||
width="200px" />
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<app-list-item
|
@for (item of list(); track item) {
|
||||||
*ngFor="let item of list"
|
<app-list-item
|
||||||
[name]="item.firstName"
|
[name]="item.firstName"
|
||||||
[id]="item.id"
|
[id]="item.id"
|
||||||
[type]="type"></app-list-item>
|
[type]="type()"></app-list-item>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -36,24 +35,23 @@ import { ListItemComponent } from '../list-item/list-item.component';
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
imports: [NgIf, NgFor, ListItemComponent],
|
imports: [ListItemComponent, NgOptimizedImage],
|
||||||
})
|
})
|
||||||
export class CardComponent {
|
export class CardComponent {
|
||||||
@Input() list: any[] | null = null;
|
private teacherStore = inject(TeacherStore);
|
||||||
@Input() type!: CardType;
|
private studentStore = inject(StudentStore);
|
||||||
@Input() customClass = '';
|
|
||||||
|
readonly list = input<any[] | null>(null);
|
||||||
|
readonly type = input.required<CardType>();
|
||||||
|
readonly customClass = input('');
|
||||||
|
|
||||||
CardType = CardType;
|
CardType = CardType;
|
||||||
|
|
||||||
constructor(
|
|
||||||
private teacherStore: TeacherStore,
|
|
||||||
private studentStore: StudentStore,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
addNewItem() {
|
addNewItem() {
|
||||||
if (this.type === CardType.TEACHER) {
|
const type = this.type();
|
||||||
|
if (type === CardType.TEACHER) {
|
||||||
this.teacherStore.addOne(randTeacher());
|
this.teacherStore.addOne(randTeacher());
|
||||||
} else if (this.type === CardType.STUDENT) {
|
} else if (type === CardType.STUDENT) {
|
||||||
this.studentStore.addOne(randStudent());
|
this.studentStore.addOne(randStudent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { StudentStore } from '../../data-access/student.store';
|
||||||
import { TeacherStore } from '../../data-access/teacher.store';
|
import { TeacherStore } from '../../data-access/teacher.store';
|
||||||
import { CardType } from '../../model/card.model';
|
import { CardType } from '../../model/card.model';
|
||||||
@@ -7,28 +12,28 @@ import { CardType } from '../../model/card.model';
|
|||||||
selector: 'app-list-item',
|
selector: 'app-list-item',
|
||||||
template: `
|
template: `
|
||||||
<div class="border-grey-300 flex justify-between border px-2 py-1">
|
<div class="border-grey-300 flex justify-between border px-2 py-1">
|
||||||
{{ name }}
|
{{ name() }}
|
||||||
<button (click)="delete(id)">
|
<button (click)="delete(id())">
|
||||||
<img class="h-5" src="assets/svg/trash.svg" />
|
<img class="h-5" src="assets/svg/trash.svg" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ListItemComponent {
|
export class ListItemComponent {
|
||||||
@Input() id!: number;
|
private teacherStore = inject(TeacherStore);
|
||||||
@Input() name!: string;
|
private studentStore = inject(StudentStore);
|
||||||
@Input() type!: CardType;
|
|
||||||
|
|
||||||
constructor(
|
readonly id = input.required<number>();
|
||||||
private teacherStore: TeacherStore,
|
readonly name = input.required<string>();
|
||||||
private studentStore: StudentStore,
|
readonly type = input.required<CardType>();
|
||||||
) {}
|
|
||||||
|
|
||||||
delete(id: number) {
|
delete(id: number) {
|
||||||
if (this.type === CardType.TEACHER) {
|
const type = this.type();
|
||||||
|
if (type === CardType.TEACHER) {
|
||||||
this.teacherStore.deleteOne(id);
|
this.teacherStore.deleteOne(id);
|
||||||
} else if (this.type === CardType.STUDENT) {
|
} else if (type === CardType.STUDENT) {
|
||||||
this.studentStore.deleteOne(id);
|
this.studentStore.deleteOne(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ While the application works, the developer experience is far from being optimal.
|
|||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
- You <b>must</b> refactor the `CardComponent` and `ListItemComponent`.
|
- 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`.
|
- 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 `NgIf` or `NgSwitch`.
|
- `CardComponent` should not contain any conditions.
|
||||||
- CSS: try to avoid using `::ng-deep`. Find a better way to handle CSS styling.
|
- CSS: try to avoid using `::ng-deep`. Find a better way to handle CSS styling.
|
||||||
|
|
||||||
## Bonus Challenges
|
## 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))
|
- 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))
|
- To reference the template, use a directive instead of magic strings ([What is wrong with magic strings?](https://softwareengineering.stackexchange.com/a/365344))
|
||||||
|
|||||||
@@ -37,12 +37,11 @@ Bien que l'application fonctionne, l'expérience développeur est loin d'être o
|
|||||||
## Contraintes
|
## Contraintes
|
||||||
|
|
||||||
- Vous <b>devez</b> refactoriser le `CardComponent` et le `ListItemComponent`.
|
- 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`.
|
- 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 aucun `NgIf` ni `NgSwitch`.
|
- 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.
|
- CSS: essayez d'éviter d'utiliser `::ng-deep`. Trouvez un meilleur moyen de gérer le style CSS.
|
||||||
|
|
||||||
## Challenges Bonus
|
## 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))
|
- 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))
|
- 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))
|
||||||
|
|||||||
Reference in New Issue
Block a user