Skip to content

Answer:1 Angular projection challenge #1334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,9 +1,47 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { tap } from 'rxjs';
import { CityStore } from '../../data-access/city.store';
import {
FakeHttpService,
randomCity,
} from '../../data-access/fake-http.service';
import { City } from '../../model/city.model';
import { CardComponent } from '../../ui/card/card.component';

@Component({
selector: 'app-city-card',
template: 'TODO City',
imports: [],
template: `
<app-card
[list]="cities()"
[cardImgSrc]="'assets/img/teacher.png'"
(delete)="delete($event)"
(addNewItem)="addNewItem()"
customClass="bg-light-red">
<ng-template #nameTemplate let-item>
<span class="name">{{ item.name }}</span>
</ng-template>
</app-card>
`,
imports: [CardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CityCardComponent {}
export class CityCardComponent {
private http = inject(FakeHttpService);
private store = inject(CityStore);

cities = this.store.cities;

fetchCities = rxResource<City[], string | undefined>({
loader: () =>
this.http.fetchCities$.pipe(tap((city) => this.store.addAll(city))),
});

addNewItem() {
this.store.addOne(randomCity());
}

delete(id: number) {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { tap } from 'rxjs';
import {
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core';
import { FakeHttpService } from '../../data-access/fake-http.service';
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';

@Component({
selector: 'app-student-card',
template: `
<app-card
[list]="students()"
[type]="cardType"
[cardImgSrc]="'assets/img/student.webp'"
(delete)="delete($event)"
(addNewItem)="addNewItem()"
customClass="bg-light-green" />
`,
styles: [
`
::ng-deep .bg-light-green {
background-color: rgba(0, 250, 0, 0.1);
}
`,
],
styles: [],
imports: [CardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StudentCardComponent implements OnInit {
export class StudentCardComponent {
private http = inject(FakeHttpService);
private store = inject(StudentStore);

students = this.store.students;
cardType = CardType.STUDENT;

ngOnInit(): void {
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
fetchStudents = rxResource<Student[], string | undefined>({
loader: () =>
this.http.fetchStudents$.pipe(
tap((student) => this.store.addAll(student)),
),
});

addNewItem() {
this.store.addOne(randStudent());
}

delete(id: number) {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import { Component, inject, OnInit } from '@angular/core';
import { FakeHttpService } from '../../data-access/fake-http.service';
import { Component, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { tap } from 'rxjs';
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';

@Component({
selector: 'app-teacher-card',
template: `
<app-card
[list]="teachers()"
[type]="cardType"
[cardImgSrc]="'assets/img/teacher.png'"
(delete)="delete($event)"
(addNewItem)="addNewItem()"
customClass="bg-light-red"></app-card>
`,
styles: [
`
::ng-deep .bg-light-red {
background-color: rgba(250, 0, 0, 0.1);
}
`,
],
imports: [CardComponent],
})
export class TeacherCardComponent implements OnInit {
export class TeacherCardComponent {
private http = inject(FakeHttpService);
private store = inject(TeacherStore);

teachers = this.store.teachers;
cardType = CardType.TEACHER;

ngOnInit(): void {
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
fetchTeachers = rxResource<Teacher[], string | undefined>({
loader: () =>
this.http.fetchTeachers$.pipe(tap((t) => this.store.addAll(t))),
});

addNewItem() {
this.store.addOne(randTeacher());
}

delete(id: number) {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { City } from '../model/city.model';
providedIn: 'root',
})
export class CityStore {
private cities = signal<City[]>([]);
public cities = signal<City[]>([]);

addAll(cities: City[]) {
this.cities.set(cities);
Expand Down
59 changes: 26 additions & 33 deletions apps/angular/1-projection/src/app/ui/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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';
import { CardType } from '../../model/card.model';
import { NgOptimizedImage, NgTemplateOutlet } from '@angular/common';
import {
Component,
contentChild,
input,
output,
TemplateRef,
} from '@angular/core';
import { ListItemComponent } from '../list-item/list-item.component';

@Component({
Expand All @@ -12,47 +14,38 @@ 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()">
@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" />
}

<img [ngSrc]="cardImgSrc()" width="200" height="200" priority />
<section>
@for (item of list(); track item) {
<app-list-item
[name]="item.firstName"
[id]="item.id"
[type]="type()"></app-list-item>
<app-list-item [id]="item.id" (delete)="delete.emit(item.id)">
<ng-container
*ngTemplateOutlet="
nameTemplate() || defaultName;
context: { $implicit: item }
"></ng-container>
</app-list-item>
}
</section>

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

<ng-template #defaultName let-item>
<span class="name">{{ item.firstName }}</span>
</ng-template>
</div>
`,
imports: [ListItemComponent, NgOptimizedImage],
imports: [ListItemComponent, NgOptimizedImage, NgTemplateOutlet],
})
export class CardComponent {
private teacherStore = inject(TeacherStore);
private studentStore = inject(StudentStore);

readonly list = input<any[] | null>(null);
readonly type = input.required<CardType>();
readonly cardImgSrc = input.required<string>();
readonly customClass = input('');

CardType = CardType;

addNewItem() {
const type = this.type();
if (type === CardType.TEACHER) {
this.teacherStore.addOne(randTeacher());
} else if (type === CardType.STUDENT) {
this.studentStore.addOne(randStudent());
}
}
nameTemplate = contentChild<TemplateRef<any>>('nameTemplate');
delete = output<number>();
addNewItem = output<void>();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import {
ChangeDetectionStrategy,
Component,
inject,
input,
output,
} from '@angular/core';
import { StudentStore } from '../../data-access/student.store';
import { TeacherStore } from '../../data-access/teacher.store';
import { CardType } from '../../model/card.model';

@Component({
selector: 'app-list-item',
template: `
<div class="border-grey-300 flex justify-between border px-2 py-1">
{{ name() }}
<button (click)="delete(id())">
<ng-content />
<button (click)="delete.emit()">
<img class="h-5" src="assets/svg/trash.svg" />
</button>
</div>
Expand All @@ -22,19 +19,7 @@ import { CardType } from '../../model/card.model';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ListItemComponent {
private teacherStore = inject(TeacherStore);
private studentStore = inject(StudentStore);

readonly id = input.required<number>();
readonly name = input.required<string>();
readonly type = input.required<CardType>();

delete(id: number) {
const type = this.type();
if (type === CardType.TEACHER) {
this.teacherStore.deleteOne(id);
} else if (type === CardType.STUDENT) {
this.studentStore.deleteOne(id);
}
}
delete = output<void>();
}
8 changes: 8 additions & 0 deletions apps/angular/1-projection/src/styles.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

.bg-light-green {
background-color: rgba(0, 250, 0, 0.1);
}

.bg-light-red {
background-color: rgba(250, 0, 0, 0.1);
}