Skip to content
Open
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,9 +1,67 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { CityStore } from '../../data-access/city.store';
import {
FakeHttpService,
randomCity,
} from '../../data-access/fake-http.service';
import { CardItemDirective } from '../../directive/card-item.directive';
import { CardComponent } from '../../ui/card/card.component';
import { ListItemComponent } from '../../ui/list-item/list-item.component';

@Component({
selector: 'app-city-card',
template: 'TODO City',
imports: [],
template: `
<app-card
[list]="cities()"
(add)="addCity()"
[style.--bg]="'rgba(250, 150, 0, 0.1)'">
<ng-template appCardItem let-item>
<app-list-item
[id]="item.id"
[name]="item.name"
(delete)="deleteCity($event)" />
</ng-template>
<ng-container card-image>
<img ngSrc="assets/img/city.png" width="200" height="200" alt="" />
</ng-container>
</app-card>
`,
imports: [
CardComponent,
ListItemComponent,
CardItemDirective,
NgOptimizedImage,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CityCardComponent {}
export class CityCardComponent {
private http = inject(FakeHttpService);
private store = inject(CityStore);

private fetchedCities = toSignal(this.http.fetchCities$);
cities = this.store.cities;

constructor() {
effect(() => {
const cities = this.fetchedCities();
if (cities) {
this.store.addAll(cities);
Comment thread
grzegorz555 marked this conversation as resolved.
}
});
}

addCity(): void {
this.store.addOne(randomCity());
}

deleteCity(id: number): void {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,67 @@
import { NgOptimizedImage } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
OnInit,
} from '@angular/core';
import { FakeHttpService } from '../../data-access/fake-http.service';
import { toSignal } from '@angular/core/rxjs-interop';
import {
FakeHttpService,
randStudent,
} from '../../data-access/fake-http.service';
import { StudentStore } from '../../data-access/student.store';
import { CardType } from '../../model/card.model';
import { CardItemDirective } from '../../directive/card-item.directive';
import { CardComponent } from '../../ui/card/card.component';
import { ListItemComponent } from '../../ui/list-item/list-item.component';

@Component({
selector: 'app-student-card',
template: `
<app-card
[list]="students()"
[type]="cardType"
customClass="bg-light-green" />
(add)="addStudent()"
[style.--bg]="'rgba(0, 250, 0, 0.1)'">
<ng-template appCardItem let-item>
<app-list-item
[id]="item.id"
[name]="item.firstName"
(delete)="deleteStudent($event)" />
</ng-template>
<ng-container card-image>
<img ngSrc="assets/img/student.webp" width="200" height="200" alt="" />
</ng-container>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-green {
background-color: rgba(0, 250, 0, 0.1);
}
`,
imports: [
CardComponent,
ListItemComponent,
CardItemDirective,
NgOptimizedImage,
],
imports: [CardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StudentCardComponent implements OnInit {
export class StudentCardComponent {
private http = inject(FakeHttpService);
private store = inject(StudentStore);

private fetchedStudents = toSignal(this.http.fetchStudents$);
students = this.store.students;
cardType = CardType.STUDENT;

ngOnInit(): void {
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
constructor() {
effect(() => {
const students = this.fetchedStudents();
if (students) {
this.store.addAll(students);
}
});
Comment thread
grzegorz555 marked this conversation as resolved.
}

addStudent(): void {
this.store.addOne(randStudent());
}

deleteStudent(id: number): void {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
import { NgOptimizedImage } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
OnInit,
} from '@angular/core';
import { FakeHttpService } from '../../data-access/fake-http.service';
import { toSignal } from '@angular/core/rxjs-interop';
import {
FakeHttpService,
randTeacher,
} from '../../data-access/fake-http.service';
import { TeacherStore } from '../../data-access/teacher.store';
import { CardType } from '../../model/card.model';
import { CardItemDirective } from '../../directive/card-item.directive';
import { CardComponent } from '../../ui/card/card.component';

import { ListItemComponent } from '../../ui/list-item/list-item.component';
@Component({
selector: 'app-teacher-card',
template: `
<app-card
[list]="teachers()"
[type]="cardType"
customClass="bg-light-red"></app-card>
(add)="addTeacher()"
[style.--bg]="'rgba(250, 0, 0, 0.1)'">
<ng-template appCardItem let-item>
<app-list-item
[id]="item.id"
[name]="item.firstName"
(delete)="deleteTeacher($event)" />
</ng-template>
<ng-container card-image>
<img ngSrc="assets/img/teacher.png" width="200" height="200" alt="" />
</ng-container>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-red {
background-color: rgba(250, 0, 0, 0.1);
}
`,
],
changeDetection: ChangeDetectionStrategy.Eager,
imports: [CardComponent],
imports: [
CardComponent,
ListItemComponent,
CardItemDirective,
NgOptimizedImage,
],
})
export class TeacherCardComponent implements OnInit {
export class TeacherCardComponent {
private http = inject(FakeHttpService);
private store = inject(TeacherStore);

private fetchedTeachers = toSignal(this.http.fetchTeachers$);
teachers = this.store.teachers;
cardType = CardType.TEACHER;

ngOnInit(): void {
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
constructor() {
effect(() => {
const teachers = this.fetchedTeachers();
if (teachers) {
this.store.addAll(teachers);
}
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

addTeacher(): void {
this.store.addOne(randTeacher());
}

deleteTeacher(id: number): void {
this.store.deleteOne(id);
}
}
10 changes: 7 additions & 3 deletions apps/angular/1-projection/src/app/data-access/city.store.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Injectable, signal } from '@angular/core';
import { Injectable, signal, untracked } from '@angular/core';
import { City } from '../model/city.model';

@Injectable({
providedIn: 'root',
})
export class CityStore {
private cities = signal<City[]>([]);
public cities = signal<City[]>([]);

addAll(cities: City[]) {
this.cities.set(cities);
const existingIds = new Set(untracked(this.cities).map((c) => c.id));
this.cities.set([
...untracked(this.cities),
...cities.filter((c) => !existingIds.has(c.id)),
]);
}

addOne(city: City) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, signal } from '@angular/core';
import { Injectable, signal, untracked } from '@angular/core';
import { Student } from '../model/student.model';

@Injectable({
Expand All @@ -8,7 +8,11 @@ export class StudentStore {
public students = signal<Student[]>([]);

addAll(students: Student[]) {
this.students.set(students);
const existingIds = new Set(untracked(this.students).map((s) => s.id));
this.students.set([
...untracked(this.students),
...students.filter((s) => !existingIds.has(s.id)),
]);
}

addOne(student: Student) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, signal } from '@angular/core';
import { Injectable, signal, untracked } from '@angular/core';
import { Teacher } from '../model/teacher.model';

@Injectable({
Expand All @@ -8,7 +8,11 @@ export class TeacherStore {
public teachers = signal<Teacher[]>([]);

addAll(teachers: Teacher[]) {
this.teachers.set(teachers);
const existingIds = new Set(untracked(this.teachers).map((t) => t.id));
this.teachers.set([
...untracked(this.teachers),
...teachers.filter((t) => !existingIds.has(t.id)),
]);
}

addOne(teacher: Teacher) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Directive, inject, TemplateRef } from '@angular/core';

@Directive({
selector: '[appCardItem]',
})
export class CardItemDirective {
templateRef = inject(TemplateRef);
}
58 changes: 20 additions & 38 deletions apps/angular/1-projection/src/app/ui/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,46 @@
import { NgOptimizedImage } from '@angular/common';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
inject,
contentChild,
input,
output,
} 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 { ListItemComponent } from '../list-item/list-item.component';
import { CardItemDirective } from '../../directive/card-item.directive';

@Component({
selector: 'app-card',
template: `
<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" alt="" />
}
@if (type() === CardType.STUDENT) {
<img ngSrc="assets/img/student.webp" width="200" height="200" alt="" />
}

class="card-container flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4">
<ng-content select="[card-image]"></ng-content>
<section>
@for (item of list(); track item) {
<app-list-item
[name]="item.firstName"
[id]="item.id"
[type]="type()"></app-list-item>
<ng-container
[ngTemplateOutlet]="itemTemplate().templateRef"
[ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
}
</section>

<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addNewItem()">
(click)="add.emit()">
Add
</button>
</div>
`,
styles: [
`
.card-container {
background: var(--bg, gray);
}
`,
],
changeDetection: ChangeDetectionStrategy.Eager,
imports: [ListItemComponent, NgOptimizedImage],
imports: [CommonModule],
})
export class CardComponent {
private teacherStore = inject(TeacherStore);
private studentStore = inject(StudentStore);

readonly list = input<any[] | null>(null);
readonly type = input.required<CardType>();
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());
}
}
readonly itemTemplate = contentChild.required(CardItemDirective);
add = output<void>();
}
Loading
Loading