From e10e654895dec9d4417f4be76838fb39176fa716 Mon Sep 17 00:00:00 2001 From: Adriana Del Teso Date: Sat, 18 May 2024 19:17:26 +0200 Subject: [PATCH] :sparkles: add loader component and service por API calls close #22 #27 --- src/components/common-styles.scss | 3 + src/components/games/games.component.html | 6 +- src/components/games/games.component.ts | 28 +++- src/components/loader/loader.component.html | 17 +++ src/components/loader/loader.component.scss | 125 ++++++++++++++++++ .../loader/loader.component.spec.ts | 23 ++++ src/components/loader/loader.component.ts | 24 ++++ src/components/oracles/oracles.component.html | 9 +- src/components/oracles/oracles.component.ts | 33 ++++- src/core/services/http.service.ts | 21 --- .../services/{ => http}/http.service.spec.ts | 0 src/core/services/http/http.service.ts | 40 ++++++ .../services/loader/loader.service.spec.ts | 16 +++ src/core/services/loader/loader.service.ts | 18 +++ 14 files changed, 330 insertions(+), 33 deletions(-) create mode 100644 src/components/loader/loader.component.html create mode 100644 src/components/loader/loader.component.scss create mode 100644 src/components/loader/loader.component.spec.ts create mode 100644 src/components/loader/loader.component.ts delete mode 100644 src/core/services/http.service.ts rename src/core/services/{ => http}/http.service.spec.ts (100%) create mode 100644 src/core/services/http/http.service.ts create mode 100644 src/core/services/loader/loader.service.spec.ts create mode 100644 src/core/services/loader/loader.service.ts diff --git a/src/components/common-styles.scss b/src/components/common-styles.scss index 718630c..d31aff9 100644 --- a/src/components/common-styles.scss +++ b/src/components/common-styles.scss @@ -84,6 +84,7 @@ p { .card { width: 80%; + margin-top: 24px; &__container { position: relative; @@ -397,6 +398,8 @@ a:active { } .filter { + margin-top: 20px; + @media screen and (max-width: 767px) { display: none; } diff --git a/src/components/games/games.component.html b/src/components/games/games.component.html index ef0e2f4..ea12372 100644 --- a/src/components/games/games.component.html +++ b/src/components/games/games.component.html @@ -1,4 +1,6 @@ -
+ + +@if(gamesList){
@@ -153,4 +155,4 @@

{{g

-
\ No newline at end of file +
} \ No newline at end of file diff --git a/src/components/games/games.component.ts b/src/components/games/games.component.ts index 88eb13b..244f736 100644 --- a/src/components/games/games.component.ts +++ b/src/components/games/games.component.ts @@ -7,6 +7,7 @@ import { ViewChildren, } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; import { FormControl, FormGroup, @@ -25,9 +26,11 @@ import { MatSelectChange, MatSelectModule } from '@angular/material/select'; import { GameCard } from '../commons.models'; import { FilterFunctionsService } from '../../core/functions/filter/filter-functions.service'; import { HighlightTextPipe } from '../../core/pipes/highlight-text/highlight-text.pipe'; -import GAMES_JSON from '../../assets/data/games.json'; import { CommonFunctionsService } from '../../core/functions/common/common-functions.service'; +import { HttpService } from '../../core/services/http/http.service'; import { ScrollToTopBtnComponent } from '../scroll-to-top-btn/scroll-to-top-btn.component'; +import { LoaderComponent } from '../loader/loader.component'; +import { LoaderService } from '../../core/services/loader/loader.service'; @Component({ selector: 'app-games', @@ -36,6 +39,8 @@ import { ScrollToTopBtnComponent } from '../scroll-to-top-btn/scroll-to-top-btn. CommonModule, FormsModule, HighlightTextPipe, + HttpClientModule, + LoaderComponent, MatButtonModule, MatCardModule, MatChipsModule, @@ -84,10 +89,29 @@ export class GamesComponent implements OnInit, AfterViewInit { constructor( public commonFunctions: CommonFunctionsService, public filterFunctions: FilterFunctionsService, + private httpDataService: HttpService, + private loaderService: LoaderService, ) {} ngOnInit(): void { - this.gamesList = this.filterFunctions.sortByNameAscending(GAMES_JSON.games); + this.gamesList = []; + this.loaderService.show(); + this.httpDataService.getGames().subscribe({ + next: (response) => { + this.gamesList = this.filterFunctions.sortByNameAscending( + response.games, + ); + this.filteredGames = this.filterFunctions.sortByNameAscending( + response.games, + ); + this.loaderService.hide(); + }, + error: (error) => { + console.error('Error fetching games data', error); + this.loaderService.hide(); + }, + }); + this.resetGamesList(); this.types = this.commonFunctions.extractUniqueValues( this.gamesList, diff --git a/src/components/loader/loader.component.html b/src/components/loader/loader.component.html new file mode 100644 index 0000000..49f6762 --- /dev/null +++ b/src/components/loader/loader.component.html @@ -0,0 +1,17 @@ +@if(loading) +{ +
+
+ L + O + A + D + I + N + G + . + . + . +
+
+} \ No newline at end of file diff --git a/src/components/loader/loader.component.scss b/src/components/loader/loader.component.scss new file mode 100644 index 0000000..a17e582 --- /dev/null +++ b/src/components/loader/loader.component.scss @@ -0,0 +1,125 @@ +@import "../../core/styles/variables"; + +.loader { + &__centered { + display: flex; + flex-direction: column; + align-items: center; + height: calc(100vh - 116px); + } + + &__spinner { + border: 16px solid $bg-color; + border-top: 16px solid $highlight-color; + border-radius: 50%; + width: 120px; + height: 120px; + animation: spin 2s linear infinite; + margin: auto; + display: block; + } + + &__text { + font-size: 38px; + color: $highlight-color; + display: flex; + align-items: center; + height: 100%; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.dot { + opacity: 0; +} + +@keyframes loading-animation { + 0% { + opacity: 0; + } + 9.09% { + opacity: 1; + } + 18.18% { + opacity: 1; + } + 27.27% { + opacity: 1; + } + 36.36% { + opacity: 1; + } + 45.45% { + opacity: 1; + } + 54.54% { + opacity: 1; + } + 63.63% { + opacity: 1; + } + 72.72% { + opacity: 1; + } + 81.81% { + opacity: 1; + } + 90.90% { + opacity: 1; + } + 100% { + opacity: 1; + } +} + +.loader__text .dot { + animation: loading-animation 1s steps(1) infinite; +} + +.loader__text .dot:nth-child(1) { + animation-delay: 0s; +} + +.loader__text .dot:nth-child(2) { + animation-delay: 0.1s; +} + +.loader__text .dot:nth-child(3) { + animation-delay: 0.2s; +} + +.loader__text .dot:nth-child(4) { + animation-delay: 0.3s; +} + +.loader__text .dot:nth-child(5) { + animation-delay: 0.4s; +} + +.loader__text .dot:nth-child(6) { + animation-delay: 0.5s; +} + +.loader__text .dot:nth-child(7) { + animation-delay: 0.6s; +} + +.loader__text .dot:nth-child(8) { + animation-delay: 0.7s; +} + +.loader__text .dot:nth-child(9) { + animation-delay: 0.8s; +} + +.loader__text .dot:nth-child(10) { + animation-delay: 0.9s; +} diff --git a/src/components/loader/loader.component.spec.ts b/src/components/loader/loader.component.spec.ts new file mode 100644 index 0000000..e1f3684 --- /dev/null +++ b/src/components/loader/loader.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoaderComponent } from './loader.component'; + +describe('LoaderComponent', () => { + let component: LoaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoaderComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/components/loader/loader.component.ts b/src/components/loader/loader.component.ts new file mode 100644 index 0000000..8573d28 --- /dev/null +++ b/src/components/loader/loader.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { LoaderService } from '../../core/services/loader/loader.service'; + +@Component({ + selector: 'app-loader', + standalone: true, + templateUrl: './loader.component.html', + styleUrls: ['./loader.component.scss'], +}) +export class LoaderComponent implements OnInit { + loading: boolean = false; + + constructor( + private cdr: ChangeDetectorRef, + private loaderService: LoaderService, + ) {} + + ngOnInit(): void { + this.loaderService.loading$.subscribe((loading) => { + this.loading = loading; + this.cdr.detectChanges(); + }); + } +} diff --git a/src/components/oracles/oracles.component.html b/src/components/oracles/oracles.component.html index f174a56..94d71de 100644 --- a/src/components/oracles/oracles.component.html +++ b/src/components/oracles/oracles.component.html @@ -1,4 +1,7 @@ -
+ + +@if(oraclesList) +{
@@ -21,7 +24,7 @@

@if(oracle.image) {
{{oracle.name}} image
+ alt="{{oracle.name}}">
} @if(oracle.artist){

}

-
\ No newline at end of file +
} \ No newline at end of file diff --git a/src/components/oracles/oracles.component.ts b/src/components/oracles/oracles.component.ts index 26928d5..53a8631 100644 --- a/src/components/oracles/oracles.component.ts +++ b/src/components/oracles/oracles.component.ts @@ -6,18 +6,28 @@ import { QueryList, ViewChildren, } from '@angular/core'; -import ORACLES_JSON from '../../assets/data/oracles.json'; import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; + import { HighlightTextPipe } from '../../core/pipes/highlight-text/highlight-text.pipe'; import { CommonFunctionsService } from '../../core/functions/common/common-functions.service'; import { FilterFunctionsService } from '../../core/functions/filter/filter-functions.service'; +import { HttpService } from '../../core/services/http/http.service'; import { ScrollToTopBtnComponent } from '../scroll-to-top-btn/scroll-to-top-btn.component'; import { OracleCard } from '../commons.models'; +import { LoaderComponent } from '../loader/loader.component'; +import { LoaderService } from '../../core/services/loader/loader.service'; @Component({ selector: 'app-oracles', standalone: true, - imports: [CommonModule, HighlightTextPipe, ScrollToTopBtnComponent], + imports: [ + CommonModule, + HighlightTextPipe, + HttpClientModule, + LoaderComponent, + ScrollToTopBtnComponent, + ], templateUrl: './oracles.component.html', styleUrl: '../common-styles.scss', }) @@ -31,12 +41,25 @@ export class OraclesComponent implements OnInit, AfterViewInit { constructor( public commonFunctions: CommonFunctionsService, public filterFunctions: FilterFunctionsService, + private httpDataService: HttpService, + private loaderService: LoaderService, ) {} ngOnInit(): void { - this.oraclesList = this.filterFunctions.sortByNameAscending( - ORACLES_JSON.oracles, - ); + this.oraclesList = []; + this.loaderService.show(); + this.httpDataService.getOracles().subscribe({ + next: (response) => { + this.oraclesList = this.filterFunctions.sortByNameAscending( + response.oracles, + ); + this.loaderService.hide(); + }, + error: (error) => { + console.error('Error fetching oracles data', error); + this.loaderService.hide(); + }, + }); } ngAfterViewInit() { diff --git a/src/core/services/http.service.ts b/src/core/services/http.service.ts deleted file mode 100644 index 7466b99..0000000 --- a/src/core/services/http.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { GameCard, OracleCard } from '../../components/commons.models'; - -@Injectable({ - providedIn: 'root', -}) -export class HttpService { - constructor(private http: HttpClient) {} - - getGamesData(): Observable<{ games: GameCard[] }> { - return this.http.get<{ games: GameCard[] }>('src/assets/data/games.json'); - } - - getOraclesData(): Observable<{ oracles: OracleCard[] }> { - return this.http.get<{ oracles: OracleCard[] }>( - 'src/assets/data/oracles.json', - ); - } -} diff --git a/src/core/services/http.service.spec.ts b/src/core/services/http/http.service.spec.ts similarity index 100% rename from src/core/services/http.service.spec.ts rename to src/core/services/http/http.service.spec.ts diff --git a/src/core/services/http/http.service.ts b/src/core/services/http/http.service.ts new file mode 100644 index 0000000..7b4aef0 --- /dev/null +++ b/src/core/services/http/http.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, catchError, map, of } from 'rxjs'; +import { GameCard, OracleCard } from '../../../components/commons.models'; + +@Injectable({ + providedIn: 'root', +}) +export class HttpService { + private gamesDb = 'assets/data/games.json'; + private oraclesDb = 'assets/data/oracles.json'; + private bggUrl = 'https://boardgamegeek.com/xmlapi2/'; + private proxyUrl = 'http://localhost:8080/'; + + constructor(private http: HttpClient) {} + + getGames(): Observable<{ games: GameCard[] }> { + return this.http.get<{ games: GameCard[] }>(this.gamesDb).pipe( + map((data) => { + if (Array.isArray(data.games)) { + return data; + } else { + return data.games; + } + }), + catchError((error) => { + console.error('Error fetching games data:', error); + return of({ games: [] }); + }), + ); + } + + getOracles(): Observable<{ oracles: OracleCard[] }> { + return this.http.get<{ oracles: OracleCard[] }>(this.oraclesDb); + } + + getBGG(): Observable { + return this.http.get(this.proxyUrl + this.bggUrl, { responseType: 'text' }); + } +} diff --git a/src/core/services/loader/loader.service.spec.ts b/src/core/services/loader/loader.service.spec.ts new file mode 100644 index 0000000..aef6961 --- /dev/null +++ b/src/core/services/loader/loader.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoaderService } from './loader.service'; + +describe('LoaderService', () => { + let service: LoaderService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(LoaderService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/core/services/loader/loader.service.ts b/src/core/services/loader/loader.service.ts new file mode 100644 index 0000000..acc39c4 --- /dev/null +++ b/src/core/services/loader/loader.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class LoaderService { + private _loading = new BehaviorSubject(false); + public readonly loading$ = this._loading.asObservable(); + + show() { + this._loading.next(true); + } + + hide() { + this._loading.next(false); + } +}