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({
|
||||
selector: 'app-city-card',
|
||||
template: 'TODO City',
|
||||
imports: [],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CityCardComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
export class CityCardComponent {}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user