Skip to content
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

Answer:47 #1206

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6c03f87
feat(projection): implement card directives and improve UI styling
Ian-Balijawa Feb 7, 2025
a4f7019
feat(challenge-3): enhance ngFor directive with empty state handling
Ian-Balijawa Feb 7, 2025
636cd8a
feat(challenge-4): implement typed template context outlets
Ian-Balijawa Feb 7, 2025
e758beb
test(challenge-5): implement unit tests for CRUD application
Ian-Balijawa Feb 7, 2025
c147b3c
feat(challenge-6): implement role-based structural directives and rou…
Ian-Balijawa Feb 7, 2025
f1b4ccb
feat(challenge-8): implement pure pipe for person display
Ian-Balijawa Feb 7, 2025
7e4655d
feat(challenge-9): implement reusable wrap function pipe
Ian-Balijawa Feb 7, 2025
0f66a4b
feat(challenge-10): implement utility wrapper pipe
Ian-Balijawa Feb 7, 2025
c8e8b9a
feat(challenge-13): implement CSS variables and host-context styling
Ian-Balijawa Feb 7, 2025
99f4639
feat(challenge-16): implement row-level currency service injection
Ian-Balijawa Feb 7, 2025
01ebdce
feat(challenge-21): implement smooth anchor navigation
Ian-Balijawa Feb 7, 2025
d7bf81e
feat(challenge-22): implement route parameter handling
Ian-Balijawa Feb 7, 2025
2970e07
feat(challenge-31): migrate app to standalone components
Ian-Balijawa Feb 7, 2025
3a94606
fix(challenge-32): resolve change detection infinite loop
Ian-Balijawa Feb 8, 2025
8d8b76c
feat(challenge-33): decouple button behavior from styling
Ian-Balijawa Feb 8, 2025
b8d43ae
feat(challenge-39): injection-token
Ian-Balijawa Feb 8, 2025
5cb4be6
feat(challenge-44): transition through states
Ian-Balijawa Feb 8, 2025
8048997
refactor(challenge-44): remove thumbnail header from post component t…
Ian-Balijawa Feb 8, 2025
32e9512
feat(challenge-45): integrate React component in Angular
Ian-Balijawa Feb 8, 2025
e04451b
feat(challenge-46): implement page load animations
Ian-Balijawa Feb 8, 2025
9287b15
feat(challenge-52): implement lazy loading with @defer
Ian-Balijawa Feb 8, 2025
cdfd3ed
feat(challenge-55): implement back button navigation handling
Ian-Balijawa Feb 8, 2025
04cbdb8
feat(challenge-41): implement ControlValueAccessor with enhanced UI
Ian-Balijawa Feb 8, 2025
f2e5687
feat(challenge-48): implement form data protection with dialog
Ian-Balijawa Feb 8, 2025
4c78c3f
refactor(challenge-47): replace enums with union and mapped types
Ian-Balijawa Feb 8, 2025
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
21 changes: 17 additions & 4 deletions apps/angular/1-projection/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@ import { TeacherCardComponent } from './component/teacher-card/teacher-card.comp
@Component({
selector: 'app-root',
template: `
<div class="grid grid-cols-3 gap-3">
<app-teacher-card />
<app-student-card />
<app-city-card />
<div class="app-container">
<div class="cards-grid">
<app-teacher-card />
<app-student-card />
<app-city-card />
</div>
</div>
`,
styles: [
`
.app-container {
@apply min-h-screen bg-gray-50 p-8;
}
.cards-grid {
@apply grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3;
@apply mx-auto max-w-7xl;
}
`,
],
imports: [TeacherCardComponent, StudentCardComponent, CityCardComponent],
})
export class AppComponent {}
Original file line number Diff line number Diff line change
@@ -1,9 +1,80 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { CityStore } from '../../data-access/city.store';
import {
FakeHttpService,
randomCity,
} from '../../data-access/fake-http.service';
import { CardType } from '../../model/card.model';
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: [],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-card [list]="cities()" [type]="cardType" customClass="city-card">
<!-- Header Template -->
<div cardHeader class="header-content">
<img
ngSrc="assets/img/city.png"
width="200"
height="200"
alt="City"
class="header-image" />
</div>

<!-- Item Template -->
<ng-template #itemTemplate let-city>
<app-list-item
[name]="city.name"
[id]="city.id"
[type]="cardType"
(delete)="deleteCity(city.id)"></app-list-item>
</ng-template>

<!-- Footer Template -->
<div cardFooter>
<button class="add-button" (click)="addCity()">Add City</button>
</div>
</app-card>
`,
styles: [
`
.city-card {
@apply bg-gradient-to-br from-blue-50 to-white;
}
.header-content {
@apply flex justify-center;
}
.header-image {
@apply rounded-lg object-cover shadow-sm;
}
.add-button {
@apply w-full rounded-md bg-blue-500 px-4 py-2 text-white;
@apply transition-colors duration-200 hover:bg-blue-600;
@apply disabled:cursor-not-allowed disabled:opacity-50;
}
`,
],
imports: [CardComponent, ListItemComponent, NgOptimizedImage],
standalone: true,
})
export class CityCardComponent {}
export class CityCardComponent implements OnInit {
private http = inject(FakeHttpService);
private store = inject(CityStore);

cities = this.store.cities;
cardType = CardType.CITY;

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

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

deleteCity(id: number) {
this.store.deleteOne(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
import { NgOptimizedImage } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
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 { 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()" [type]="cardType" customClass="student-card">
<!-- Header Template -->
<div cardHeader class="header-content">
<img
ngSrc="assets/img/student.webp"
width="200"
height="200"
alt="Student"
class="header-image" />
</div>

<!-- Item Template -->
<ng-template #itemTemplate let-student>
<app-list-item
[name]="student.firstName"
[id]="student.id"
[type]="cardType"
(delete)="deleteStudent(student.id)"></app-list-item>
</ng-template>

<!-- Footer Template -->
<div cardFooter>
<button class="add-button" (click)="addStudent()">Add Student</button>
</div>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-green {
background-color: rgba(0, 250, 0, 0.1);
.student-card {
@apply bg-gradient-to-br from-green-50 to-white;
}
.header-content {
@apply flex justify-center;
}
.header-image {
@apply rounded-lg object-cover shadow-sm;
}
.add-button {
@apply w-full rounded-md bg-blue-500 px-4 py-2 text-white;
@apply transition-colors duration-200 hover:bg-blue-600;
@apply disabled:cursor-not-allowed disabled:opacity-50;
}
`,
],
imports: [CardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CardComponent, ListItemComponent, NgOptimizedImage],
standalone: true,
})
export class StudentCardComponent implements OnInit {
private http = inject(FakeHttpService);
Expand All @@ -37,4 +69,12 @@ export class StudentCardComponent implements OnInit {
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 number Diff line number Diff line change
@@ -1,25 +1,63 @@
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 { 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()" [type]="cardType" customClass="teacher-card">
<!-- Header Template -->
<div cardHeader class="header-content">
<img
ngSrc="assets/img/teacher.png"
width="200"
height="200"
alt="Teacher"
class="header-image" />
</div>

<!-- Item Template -->
<ng-template #itemTemplate let-teacher>
<app-list-item
[name]="teacher.firstName"
[id]="teacher.id"
[type]="cardType"
(delete)="deleteTeacher(teacher.id)"></app-list-item>
</ng-template>

<!-- Footer Template -->
<div cardFooter>
<button class="add-button" (click)="addTeacher()">Add Teacher</button>
</div>
</app-card>
`,
styles: [
`
::ng-deep .bg-light-red {
background-color: rgba(250, 0, 0, 0.1);
.teacher-card {
@apply bg-gradient-to-br from-red-50 to-white;
}
.header-content {
@apply flex justify-center;
}
.header-image {
@apply rounded-lg object-cover shadow-sm;
}
.add-button {
@apply w-full rounded-md bg-blue-500 px-4 py-2 text-white;
@apply transition-colors duration-200 hover:bg-blue-600;
@apply disabled:cursor-not-allowed disabled:opacity-50;
}
`,
],
imports: [CardComponent],
imports: [CardComponent, ListItemComponent, NgOptimizedImage],
standalone: true,
})
export class TeacherCardComponent implements OnInit {
private http = inject(FakeHttpService);
Expand All @@ -31,4 +69,12 @@ export class TeacherCardComponent implements OnInit {
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 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
Loading
Loading