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 numberDiff line numberDiff line change
@@ -1,9 +1,75 @@
import{ChangeDetectionStrategy, Component } from '@angular/core'
import{NgOptimizedImage } from '@angular/common'
import{
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core'
import{CityStore } from '../../data-access/city.store'
import{
FakeHttpService,
randomCity,
} from '../../data-access/fake-http.service'
import{CardActionsDirective } from '../../ui/card/card-actions.directive'
import{CardImageDirective } from '../../ui/card/card-image.directive'
import{CardListItemDirective } from '../../ui/card/card-list-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: [],
standalone: true,
imports: [
NgOptimizedImage,
CardComponent,
ListItemComponent,
CardImageDirective,
CardListItemDirective,
CardActionsDirective,
],
template: `
<app-card [list]="cities()" class="bg-light-blue">
<ng-template cardImage>
<img ngSrc="assets/img/city.png" width="200" height="200" alt="City" />
</ng-template>

<ng-template cardListItem let-city>
<app-list-item (onDelete)="deleteCity(city.id)">
{{city.name }}
</app-list-item>
</ng-template>

<ng-template cardActions>
<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2 hover:bg-blue-400"
(click)="addCity()">
Add City
</button>
</ng-template>
</app-card>
`,
styles: `
.bg-light-blue{
background-color: rgba(0, 0, 250, 0.1);
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CityCardComponent{}
export class CityCardComponent implements OnInit{
private readonly http = inject(FakeHttpService);
private readonly store = inject(CityStore);

cities = this.store.cities;

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

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

deleteCity(id: number): void{
this.store.deleteOne(id);
}
}
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,80 @@
import{NgOptimizedImage } from '@angular/common'
import{
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core'
import{FakeHttpService } from '../../data-access/fake-http.service'
import{
FakeHttpService,
randStudent,
} from '../../data-access/fake-http.service'
import{StudentStore } from '../../data-access/student.store'
import{CardType } from '../../model/card.model'
import{CardActionsDirective } from '../../ui/card/card-actions.directive'
import{CardImageDirective } from '../../ui/card/card-image.directive'
import{CardListItemDirective } from '../../ui/card/card-list-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" />
<app-card [list]="students()" class="bg-light-green">
<ng-template cardImage>
<img
ngSrc="assets/img/student.webp"
width="200"
height="200"
alt="Student" />
</ng-template>

<ng-template cardListItem let-student>
<app-list-item (onDelete)="deleteStudent(student.id)">
{{student.firstName }}
</app-list-item>
</ng-template>

<ng-template cardActions>
<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addStudent()">
Add Student
</button>
</ng-template>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-green{
.bg-light-green{
background-color: rgba(0, 250, 0, 0.1);
}
`,
],
imports: [CardComponent],
imports: [
CardComponent,
ListItemComponent,
NgOptimizedImage,
CardImageDirective,
CardListItemDirective,
CardActionsDirective,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StudentCardComponent implements OnInit{
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));
}

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

deleteStudent(id: number){
this.store.deleteOne(id);
}
}
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,74 @@
import{NgOptimizedImage } from '@angular/common'
import{Component, inject, OnInit } from '@angular/core'
import{FakeHttpService } from '../../data-access/fake-http.service'
import{
FakeHttpService,
randTeacher,
} from '../../data-access/fake-http.service'
import{TeacherStore } from '../../data-access/teacher.store'
import{CardType } from '../../model/card.model'
import{CardActionsDirective } from '../../ui/card/card-actions.directive'
import{CardImageDirective } from '../../ui/card/card-image.directive'
import{CardListItemDirective } from '../../ui/card/card-list-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>
<app-card [list]="teachers()" class="bg-light-red">
<ng-template cardImage>
<img
ngSrc="assets/img/teacher.png"
width="200"
height="200"
alt="Teacher" />
</ng-template>

<ng-template cardListItem let-teacher>
<app-list-item (onDelete)="deleteTeacher(teacher.id)">
{{teacher.firstName }}
</app-list-item>
</ng-template>

<ng-template cardActions>
<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addTeacher()">
Add Teacher
</button>
</ng-template>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-red{
.bg-light-red{
background-color: rgba(250, 0, 0, 0.1);
}
`,
],
imports: [CardComponent],
imports: [
NgOptimizedImage,
CardComponent,
ListItemComponent,
CardImageDirective,
CardListItemDirective,
CardActionsDirective,
],
})
export class TeacherCardComponent implements OnInit{
private http = inject(FakeHttpService);
private store = inject(TeacherStore);
private readonly http = inject(FakeHttpService);
private readonly store = inject(TeacherStore);

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

ngOnInit(): void{
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
}

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

deleteTeacher(id: number){
this.store.deleteOne(id);
}
}
Original file line numberDiff line numberDiff 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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import{Directive, TemplateRef } from '@angular/core'

@Directive({
selector: 'ng-template[cardActions]',
standalone: true,
})
export class CardActionsDirective{
constructor(public templateRef: TemplateRef<unknown>){}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import{Directive, TemplateRef } from '@angular/core'

@Directive({
selector: 'ng-template[cardImage]',
standalone: true,
})
export class CardImageDirective{
constructor(public templateRef: TemplateRef<unknown>){}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import{Directive, TemplateRef } from '@angular/core'

@Directive({
selector: 'ng-template[cardListItem]',
standalone: true,
})
export class CardListItemDirective{
constructor(public templateRef: TemplateRef<unknown>){}
}
77 changes: 31 additions & 46 deletions apps/angular/1-projection/src/app/ui/card/card.component.ts
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,43 @@
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{ListItemComponent } from '../list-item/list-item.component'
import{NgTemplateOutlet } from '@angular/common'
import{Component, ContentChild, input } from '@angular/core'
import{CardActionsDirective } from './card-actions.directive'
import{CardImageDirective } from './card-image.directive'
import{CardListItemDirective } from './card-list-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" />
}
@if (type() === CardType.STUDENT){
<img ngSrc="assets/img/student.webp" width="200" height="200" />
}
@if (imageTemplate){
<ng-container *ngTemplateOutlet="imageTemplate.templateRef" />
}

<section>
@for (item of list(); track item){
<app-list-item
[name]="item.firstName"
[id]="item.id"
[type]="type()"></app-list-item>
}
</section>
<section>
@for (item of list(); track trackByFn(item)){
<ng-container
*ngTemplateOutlet="
listItemTemplate?.templateRef;
context:{$implicit: item }
" />
}
</section>

<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="addNewItem()">
Add
</button>
</div>
@if (actionsTemplate){
<ng-container *ngTemplateOutlet="actionsTemplate.templateRef" />
}
`,
imports: [ListItemComponent, NgOptimizedImage],
host:{
class: 'flex w-fit flex-col gap-3 rounded-md border-2 border-black p-4',
},
imports: [NgTemplateOutlet],
})
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('');
export class CardComponent<T extends{id: number | string }>{
readonly list = input<T[] | null>(null);

CardType = CardType;
@ContentChild(CardImageDirective) imageTemplate?: CardImageDirective;
@ContentChild(CardListItemDirective) listItemTemplate?: CardListItemDirective;
@ContentChild(CardActionsDirective) actionsTemplate?: CardActionsDirective;

addNewItem(){
const type = this.type();
if (type === CardType.TEACHER){
this.teacherStore.addOne(randTeacher());
} else if (type === CardType.STUDENT){
this.studentStore.addOne(randStudent());
}
protected trackByFn(item: T): number | string{
return item.id;
}
}
Loading