Skip to content
Closed

Develop #1526

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { CityStore } from '../../data-access/city.store';
import { FakeHttpService } from '../../data-access/fake-http.service';
import { CardType } from '../../model/card.model';
import { City } from '../../model/city.model';
import { CardComponent } from '../../ui/card/card.component';

@Component({
selector: 'app-city-card',
template: 'TODO City',
template: `
<app-card
[list]="cities"
[type]="cardType"
[image]="image"
customClass="bg-light-red"></app-card>
Comment on lines +11 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reused bg-light-red class from TeacherCardComponent.

Per the graph context, TeacherCardComponent already uses customClass="bg-light-red". StudentCardComponent uses a distinct bg-light-green. Reusing bg-light-red for CityCardComponent makes city and teacher cards visually indistinguishable — likely a copy/paste oversight rather than an intentional shared style.

🎨 Suggested fix
     <app-card
       [list]="cities"
       [type]="cardType"
       [image]="image"
-      customClass="bg-light-red"></app-card>
+      customClass="bg-light-blue"></app-card>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<app-card
[list]="cities"
[type]="cardType"
[image]="image"
customClass="bg-light-red"></app-card>
<app-card
[list]="cities"
[type]="cardType"
[image]="image"
customClass="bg-light-blue"></app-card>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/angular/projection/src/app/component/city-card/city-card.component.ts`
around lines 11 - 15, `CityCardComponent` is reusing the `bg-light-red`
customClass from `TeacherCardComponent`, which makes the city card look
identical to the teacher card. Update the `app-card` usage in
`CityCardComponent` to use a city-specific class instead of `bg-light-red`,
following the pattern used by `StudentCardComponent` and the other card
components so each card type remains visually distinct.

`,
standalone: true,
imports: [],
imports: [CardComponent],
})
export class CityCardComponent implements OnInit {
constructor() {}
cities: City[] = [];
image = 'city.png';
cardType = CardType.CITY;

ngOnInit(): void {}
constructor(
private http: FakeHttpService,
private store: CityStore,
) {}

ngOnInit(): void {
this.http.fetchCities$.subscribe((s) => this.store.addAll(s));

this.store.cities$.subscribe((s) => (this.cities = s));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CardComponent } from '../../ui/card/card.component';
<app-card
[list]="students"
[type]="cardType"
[image]="image"
customClass="bg-light-green"></app-card>
`,
standalone: true,
Expand All @@ -26,6 +27,7 @@ import { CardComponent } from '../../ui/card/card.component';
export class StudentCardComponent implements OnInit {
students: Student[] = [];
cardType = CardType.STUDENT;
image = 'student.webp';

constructor(
private http: FakeHttpService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CardComponent } from '../../ui/card/card.component';
<app-card
[list]="teachers"
[type]="cardType"
[image]="image"
customClass="bg-light-red"></app-card>
`,
styles: [
Expand All @@ -26,6 +27,7 @@ import { CardComponent } from '../../ui/card/card.component';
export class TeacherCardComponent implements OnInit {
teachers: Teacher[] = [];
cardType = CardType.TEACHER;
image = 'teacher.png';

constructor(
private http: FakeHttpService,
Expand Down
8 changes: 8 additions & 0 deletions apps/angular/projection/src/app/model/card.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ export enum CardType {
STUDENT,
CITY,
}

export interface CardModel {
name: string;
firstName: string;
lastName: string;
subject: string;
id: number;
}
31 changes: 31 additions & 0 deletions apps/angular/projection/src/app/services/card.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { CityStore } from '../data-access/city.store';
import {
randomCity,
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';

@Injectable({
providedIn: 'root',
})
export class CardService {
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
private cityStore: CityStore,
) {}

addOne(type: CardType) {
if (type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
} else if (type === CardType.CITY) {
this.cityStore.addOne(randomCity());
}
}
}
61 changes: 24 additions & 37 deletions apps/angular/projection/src/app/ui/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { NgFor, NgIf } from '@angular/common';
import { CommonModule } 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 { CardService } from '../../services/card.service';
import { ListItemComponent } from '../list-item/list-item.component';

@Component({
Expand All @@ -12,50 +10,39 @@ import { ListItemComponent } from '../list-item/list-item.component';
<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" />
<ng-template #cardContent>
<img src="assets/img/{{ image }}" width="200px" />

<section>
<app-list-item
*ngFor="let item of list"
[name]="item.firstName"
[id]="item.id"
[type]="type"></app-list-item>
</section>
<section>
<app-list-item
*ngFor="let item of list"
[name]="item.firstName ?? item.name"
[id]="item.id"
[type]="type"></app-list-item>
</section>

<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addNewItem()">
Add
</button>
<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addNewItem()">
Add
</button>
</ng-template>

<ng-container *ngTemplateOutlet="cardContent"></ng-container>
</div>
Comment on lines +13 to 32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target component and nearby context
git ls-files apps/angular/projection/src/app/ui/card
echo "--- card.component.ts ---"
cat -n apps/angular/projection/src/app/ui/card/card.component.ts

echo "--- search for ng-content / ngTemplateOutlet in projection app ---"
rg -n "ng-content|ngTemplateOutlet|cardContent|CardType|alt=" apps/angular/projection/src/app -S

Repository: tomalaforge/angular-challenges

Length of output: 4295


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- usages of app-card ---"
rg -n "<app-card|selector: 'app-card'|selector: \"app-card\"" apps/angular/projection/src/app -S

echo "--- card-related component files ---"
git ls-files apps/angular/projection/src/app/component apps/angular/projection/src/app/ui

echo "--- card component and likely callers ---"
sed -n '1,220p' apps/angular/projection/src/app/component/city-card/city-card.component.ts
printf '\n---\n'
sed -n '1,220p' apps/angular/projection/src/app/component/student-card/student-card.component.ts
printf '\n---\n'
sed -n '1,220p' apps/angular/projection/src/app/component/teacher-card/teacher-card.component.ts

Repository: tomalaforge/angular-challenges

Length of output: 4184


Add an alt attribute and remove the extra template wrapper

  • The card image needs an alt attribute for screen readers, e.g. alt="{{ type }} card".
  • #cardContent is rendered immediately via *ngTemplateOutlet, so the extra ng-template layer can be inlined unless projected content is coming later.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/angular/projection/src/app/ui/card/card.component.ts` around lines 13 -
32, The card template in card.component.ts should be simplified and made
accessible: add an alt attribute to the img element (use the card’s type to
describe it), and remove the unnecessary `#cardContent` ng-template wrapper since
the content is rendered immediately via ngTemplateOutlet. Update the template
structure so the image, list, and Add button live in the direct card content
instead of being wrapped in an extra template layer.

`,
standalone: true,
imports: [NgIf, NgFor, ListItemComponent],
imports: [CommonModule, ListItemComponent],
})
export class CardComponent {
@Input() list: any[] | null = null;
@Input() list!: any[];
@Input() type!: CardType;
@Input() customClass = '';
@Input() image: string = '';

CardType = CardType;

constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
) {}
constructor(private cardService: CardService) {}

addNewItem() {
if (this.type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (this.type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
}
this.cardService.addOne(this.type);
}
Comment on lines 37 to 47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use CardModel[] instead of any[] for list.

list is typed any[], discarding the type safety CardModel (name, firstName, lastName, subject, id) was introduced for. Accesses like item.firstName, item.name, item.id in the template go unchecked. Since the input is now effectively required (non-null asserted), consider also using Angular's @Input({ required: true }) (supported since v16, confirmed for this project's Angular 17.1.0) so the compiler enforces the binding instead of relying solely on the ! assertion.

♻️ Proposed fix
+import { CardModel } from '../../model/card.model';
 ...
-  `@Input`() list!: any[];
+  `@Input`({ required: true }) list!: CardModel[];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class CardComponent {
@Input() list: any[] | null = null;
@Input() list!: any[];
@Input() type!: CardType;
@Input() customClass = '';
@Input() image: string = '';
CardType = CardType;
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
) {}
constructor(private cardService: CardService) {}
addNewItem() {
if (this.type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (this.type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
}
this.cardService.addOne(this.type);
}
import { CardModel } from '../../model/card.model';
export class CardComponent {
`@Input`({ required: true }) list!: CardModel[];
`@Input`() type!: CardType;
`@Input`() customClass = '';
`@Input`() image: string = '';
constructor(private cardService: CardService) {}
addNewItem() {
this.cardService.addOne(this.type);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/angular/projection/src/app/ui/card/card.component.ts` around lines 37 -
47, The CardComponent input `list` is still declared as `any[]`, which removes
the type safety introduced by `CardModel` and leaves template accesses like
`item.name` and `item.firstName` unchecked. Update the `CardComponent` `list`
input to use `CardModel[]` and, since the binding is required, switch it to
Angular’s `@Input({ required: true })` instead of relying on the non-null
assertion. Keep the existing `CardComponent` API and `CardService` usage
unchanged.

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, Input } from '@angular/core';
import { CityStore } from '../../data-access/city.store';
import { StudentStore } from '../../data-access/student.store';
import { TeacherStore } from '../../data-access/teacher.store';
import { CardType } from '../../model/card.model';
Expand All @@ -23,13 +24,16 @@ export class ListItemComponent {
constructor(
private teacherStore: TeacherStore,
private studentStore: StudentStore,
private cityStore: CityStore,
) {}

delete(id: number) {
if (this.type === CardType.TEACHER) {
this.teacherStore.deleteOne(id);
} else if (this.type === CardType.STUDENT) {
this.studentStore.deleteOne(id);
} else if (this.type === CardType.CITY) {
this.cityStore.deleteOne(id);
}
}
}
Loading