1- import { inject , Injectable } from '@angular/core' ;
2- import {
3- ComponentStore ,
4- OnStateInit ,
5- OnStoreInit ,
6- } from '@ngrx/component-store' ;
1+ import { computed , inject , InjectionToken } from '@angular/core' ;
2+ import { rxResource } from '@angular/core/rxjs-interop' ;
73import { tapResponse } from '@ngrx/operators' ;
8- import { pipe } from 'rxjs' ;
9- import { filter , mergeMap , tap } from 'rxjs/operators' ;
4+ import {
5+ patchState ,
6+ signalStore ,
7+ withComputed ,
8+ withHooks ,
9+ withMethods ,
10+ withProps ,
11+ withState ,
12+ } from '@ngrx/signals' ;
13+ import { of } from 'rxjs' ;
1014import { Photo } from '../photo.model' ;
1115import { PhotoService } from '../photos.service' ;
1216
@@ -17,107 +21,37 @@ export interface PhotoState {
1721 search : string ;
1822 page : number ;
1923 pages : number ;
20- loading : boolean ;
21- error : unknown ;
2224}
2325
2426const initialState : PhotoState = {
2527 photos : [ ] ,
2628 search : '' ,
2729 page : 1 ,
2830 pages : 1 ,
29- loading : false ,
30- error : '' ,
3131} ;
3232
33- @Injectable ( )
34- export class PhotoStore
35- extends ComponentStore < PhotoState >
36- implements OnStoreInit , OnStateInit
37- {
38- private photoService = inject ( PhotoService ) ;
39-
40- private readonly photos$ = this . select ( ( s ) => s . photos ) ;
41- private readonly search$ = this . select ( ( s ) => s . search ) ;
42- private readonly page$ = this . select ( ( s ) => s . page ) ;
43- private readonly pages$ = this . select ( ( s ) => s . pages ) ;
44- private readonly error$ = this . select ( ( s ) => s . error ) ;
45- private readonly loading$ = this . select ( ( s ) => s . loading ) ;
46-
47- private readonly endOfPage$ = this . select (
48- this . page$ ,
49- this . pages$ ,
50- ( page , pages ) => page === pages ,
51- ) ;
52-
53- readonly vm$ = this . select (
54- {
55- photos : this . photos$ ,
56- search : this . search$ ,
57- page : this . page$ ,
58- pages : this . pages$ ,
59- endOfPage : this . endOfPage$ ,
60- loading : this . loading$ ,
61- error : this . error$ ,
62- } ,
63- { debounce : true } ,
64- ) ;
65-
66- ngrxOnStoreInit ( ) {
67- const savedJSONState = localStorage . getItem ( PHOTO_STATE_KEY ) ;
68- if ( savedJSONState === null ) {
69- this . setState ( initialState ) ;
70- } else {
71- const savedState = JSON . parse ( savedJSONState ) ;
72- this . setState ( {
73- ...initialState ,
74- search : savedState . search ,
75- page : savedState . page ,
76- } ) ;
77- }
78- }
33+ const PHOTO_STATE = new InjectionToken < PhotoState > ( 'PhotoState' , {
34+ factory : ( ) => initialState ,
35+ } ) ;
7936
80- ngrxOnStateInit ( ) {
81- this . searchPhotos (
82- this . select ( {
83- search : this . search$ ,
84- page : this . page$ ,
37+ export const PhotoStore = signalStore (
38+ withState ( ( ) => inject ( PHOTO_STATE ) ) ,
39+ withProps ( ( store , photoService = inject ( PhotoService ) ) => ( {
40+ photosResource : rxResource ( {
41+ params : ( ) => ( {
42+ search : store . search ( ) ,
43+ page : store . page ( ) ,
8544 } ) ,
86- ) ;
87- }
88-
89- readonly search = this . updater (
90- ( state , search : string ) : PhotoState => ( {
91- ...state ,
92- search,
93- page : 1 ,
94- } ) ,
95- ) ;
96-
97- readonly nextPage = this . updater (
98- ( state ) : PhotoState => ( {
99- ...state ,
100- page : state . page + 1 ,
101- } ) ,
102- ) ;
103-
104- readonly previousPage = this . updater (
105- ( state ) : PhotoState => ( {
106- ...state ,
107- page : state . page - 1 ,
108- } ) ,
109- ) ;
45+ stream : ( { params : { search, page } } ) => {
46+ console . log ( 'Searching for:' , search , 'Page:' , page ) ;
47+ if ( search !== '' && ( search == undefined || search . length < 3 ) ) {
48+ return of ( undefined ) ;
49+ }
11050
111- readonly searchPhotos = this . effect < { search : string ; page : number } > (
112- pipe (
113- filter ( ( { search } ) => search . length >= 3 ) ,
114- tap ( ( ) => this . patchState ( { loading : true , error : '' } ) ) ,
115- mergeMap ( ( { search, page } ) =>
116- this . photoService . searchPublicPhotos ( search , page ) . pipe (
117- tapResponse (
118- ( { photos : { photo, pages } } ) => {
119- this . patchState ( {
120- loading : false ,
51+ return photoService . searchPublicPhotos ( search , page ) . pipe (
52+ tapResponse ( {
53+ next : ( { photos : { photo, pages } } ) => {
54+ patchState ( store , {
12155 photos : photo ,
12256 pages,
12357 } ) ;
@@ -126,10 +60,46 @@ export class PhotoStore
12660 JSON . stringify ( { search, page } ) ,
12761 ) ;
12862 } ,
129- ( error : unknown ) => this . patchState ( { error, loading : false } ) ,
130- ) ,
131- ) ,
132- ) ,
133- ) ,
134- ) ;
135- }
63+ error : ( error : unknown ) => {
64+ console . error ( 'Photo search error:' , error ) ;
65+ } ,
66+ } ) ,
67+ ) ;
68+ } ,
69+ } ) ,
70+ } ) ) ,
71+ withComputed ( ( { photosResource, page, pages } ) => ( {
72+ loading : computed ( ( ) => photosResource . isLoading ( ) ) ,
73+ error : computed ( ( ) => photosResource . error ( ) ) ,
74+ endOfPage : computed ( ( ) => page ( ) === pages ( ) ) ,
75+ } ) ) ,
76+ withMethods ( ( store ) => ( {
77+ setSearch ( search : string ) {
78+ patchState ( store , { search, page : 1 } ) ;
79+ } ,
80+ nextPage ( ) : void {
81+ patchState ( store , ( state ) => ( {
82+ ...state ,
83+ page : state . page + 1 ,
84+ } ) ) ;
85+ } ,
86+ previousPage ( ) : void {
87+ patchState ( store , ( state ) => ( {
88+ ...state ,
89+ page : state . page - 1 ,
90+ } ) ) ;
91+ } ,
92+ } ) ) ,
93+ withHooks ( ( store ) => ( {
94+ onInit ( ) {
95+ const savedJSONState = localStorage . getItem ( PHOTO_STATE_KEY ) ;
96+ if ( savedJSONState !== null ) {
97+ const savedState = JSON . parse ( savedJSONState ) ;
98+ patchState ( store , {
99+ search : savedState . search ,
100+ page : savedState . page ,
101+ } ) ;
102+ }
103+ } ,
104+ } ) ) ,
105+ ) ;
0 commit comments