-
Notifications
You must be signed in to change notification settings - Fork 10
Exercise 4 - Templates - Socks Shop #16
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
base: master
Are you sure you want to change the base?
Changes from all commits
c7f00de
fd17ac6
d3711d8
590b106
4dd8a00
91bbc92
8fb91ba
0619dfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,5 +100,8 @@ | |
| } | ||
| } | ||
| } | ||
| }, | ||
| "cli": { | ||
| "analytics": false | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { Pipe, PipeTransform } from '@angular/core'; | ||
|
|
||
| @Pipe({ | ||
| name: 'price', | ||
| standalone: true | ||
| }) | ||
| export class PricePipe implements PipeTransform { | ||
|
|
||
| transform(value: number): string { | ||
| if (value) { | ||
| return `€${value.toFixed(2)}`; | ||
| } else { | ||
| return '€0.00'; | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,16 +5,33 @@ <h2> | |
| Our Socks | ||
| </h2> | ||
| </div> | ||
| <div> | ||
| <label> | ||
| Filter by Name: | ||
| <input type="text" (input)="onFilterName($event)"> | ||
| </label> | ||
| <label> | ||
| Filter by Color: | ||
| <input type="text" (input)="onFilterColor($event)"> | ||
| </label> | ||
| <label> | ||
| Sort by: | ||
| <select (change)="onSortBy($event)"> | ||
| <option value="name">Name</option> | ||
| <option value="price">Price</option> | ||
| </select> | ||
| </label> | ||
| </div> | ||
| <div class="row"> | ||
| <div class="col-sm-6 col-md-4 col-lg-3" *ngFor="let sock of socks$ | async"> | ||
| <div class="col-sm-6 col-md-4 col-lg-3" *ngFor="let sock of filteredSocks$ | async"> | ||
| <div class="box"> | ||
| <a href="/socks/{{ sock.id }}"> | ||
| <div class="img-box"> | ||
| <img src="sock-images/Socks-{{ sock.brand }}-{{ sock.variant }}.png" /> | ||
| </div> | ||
| <div class="detail-box"> | ||
| <h6>{{ sock.name }}</h6> | ||
| <h6><span>{{ sock.price }}</span></h6> | ||
| <h6><span>{{ sock.price | price }}</span></h6> | ||
| </div> | ||
| <div class="new"> | ||
| <span class="favourite" [style.color]="sock.color"><i class="fa fa-star"></i></span> | ||
|
|
@@ -24,4 +41,8 @@ <h6><span>{{ sock.price }}</span></h6> | |
| </div> | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <button (click)="onPageChange(currentPageSubject.value - 1)" [disabled]="currentPageSubject.value === 1">Previous</button> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use van |
||
| <button (click)="onPageChange(currentPageSubject.value + 1)">Next</button> | ||
| </div> | ||
| </section> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,78 @@ | ||
| import { Component } from '@angular/core'; | ||
| import { SocksService } from './socks.service'; | ||
| import { Observable } from 'rxjs'; | ||
| import { Observable,BehaviorSubject, combineLatest } from 'rxjs'; | ||
| import { Sock } from './sock.model'; | ||
| import { AsyncPipe, NgFor } from '@angular/common'; | ||
| import { NgxPaginationModule } from 'ngx-pagination'; | ||
| import { map } from 'rxjs/operators'; | ||
| import { PricePipe } from '../price.pipe'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-shop', | ||
| standalone: true, | ||
| imports: [NgFor, AsyncPipe], | ||
| imports: [NgFor, AsyncPipe, NgxPaginationModule, PricePipe], | ||
| templateUrl: './shop.component.html' | ||
| }) | ||
| export class ShopComponent { | ||
| socks$!: Observable<Sock[]>; | ||
| filteredSocks$!: Observable<Sock[]>; | ||
|
|
||
| public nameFilterSubject = new BehaviorSubject<string>(''); | ||
| public colorFilterSubject = new BehaviorSubject<string>(''); | ||
| public sortBySubject = new BehaviorSubject<string>('name'); | ||
| public currentPageSubject = new BehaviorSubject<number>(1); | ||
| public pageSize = 10; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Public is default, je hoeft dit dus niet speciaal te vermelden |
||
|
|
||
| constructor(private socksService: SocksService) {} | ||
|
|
||
| ngOnInit(): void { | ||
| this.socks$ = this.socksService.get(); | ||
|
|
||
| this.filteredSocks$ = combineLatest([ | ||
| this.socks$, | ||
| this.nameFilterSubject.asObservable(), | ||
| this.colorFilterSubject.asObservable(), | ||
| this.sortBySubject.asObservable(), | ||
| this.currentPageSubject.asObservable() | ||
| ]).pipe( | ||
| map(([socks, nameFilter, colorFilter, sortBy, currentPage]) => { | ||
| let filtered = socks.filter(sock => | ||
| sock.name.toLowerCase().includes(nameFilter.toLowerCase()) && | ||
| sock.variant.toLowerCase().includes(colorFilter.toLowerCase()) | ||
| ); | ||
|
|
||
| filtered = filtered.sort((a, b) => { | ||
| if (sortBy === 'name') { | ||
| return a.name.localeCompare(b.name); | ||
| } else if (sortBy === 'price') { | ||
| return a.price - b.price; | ||
| } | ||
| return 0; | ||
| }); | ||
|
|
||
| const startIndex = (currentPage - 1) * this.pageSize; | ||
| return filtered.slice(startIndex, startIndex + this.pageSize); | ||
| }) | ||
| ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Components, components, components!! Je zit in een vrij groot Zulke components zijn moeilijk testbaar (als er ooit testen zouden geschreven worden ;) )
SRP --> Maak meer en kleinere componenten die elk een heel duidelijk afgelijnd doel/purpose hebben |
||
| } | ||
|
|
||
| onFilterName(event: Event) { | ||
| const name = (event.target as HTMLInputElement).value; | ||
| this.nameFilterSubject.next(name); | ||
| } | ||
|
|
||
| onFilterColor(event: Event) { | ||
| const color = (event.target as HTMLInputElement).value; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dit is een beetje een probleem in Angular, je zou hierrond kunnen werken met wat TypeScript magic: Maar of dat dan beter is.... |
||
| this.colorFilterSubject.next(color); | ||
| } | ||
|
|
||
| onSortBy(event: Event) { | ||
| const sortBy = (event.target as HTMLInputElement).value; | ||
| this.sortBySubject.next(sortBy); | ||
| } | ||
|
|
||
| onPageChange(page: number) { | ||
| this.currentPageSubject.next(page); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { Pipe, PipeTransform } from '@angular/core'; | ||
|
|
||
| @Pipe({ | ||
| name: 'timeAgo', | ||
| standalone: true | ||
| }) | ||
| export class TimeAgoPipe implements PipeTransform { | ||
|
|
||
| transform(value: number | Date | string): string { | ||
| if (!value) return 'N/A'; | ||
|
|
||
| const time = new Date(value).getTime(); | ||
| const now = new Date().getTime(); | ||
| const difference = Math.floor((now - time) / 1000); | ||
|
|
||
| if (difference < 30) { | ||
| return 'Just now'; | ||
| } else if (difference < 60) { | ||
| return 'A few seconds ago'; | ||
| } else if (difference < 3600) { | ||
| const minutes = Math.floor(difference / 60); | ||
| return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; | ||
| } else if (difference < 86400) { | ||
| const hours = Math.floor(difference / 3600); | ||
| return `${hours} hour${hours > 1 ? 's' : ''} ago`; | ||
| } else { | ||
| const days = Math.floor(difference / 86400); | ||
| return `${days} day${days > 1 ? 's' : ''} ago`; | ||
| } | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hier wel opletten!!
Als je een
<a href="">doet, dan gaat de browser daar naartoe navigeren, je hebt daarmee een page reload!!Ofte: je verliest de app state + een re-render van de volledige pagina
Je moet met
routerLinkwerken zodat je binnen de SPA blijft!