1- import { computed , effect , inject , Injectable , signal } from '@angular/core' ;
1+ import { inject , Injectable , signal } from '@angular/core' ;
22import { HttpClient } from '@angular/common/http' ;
3- import { Contact } from './contact' ;
3+ import { Contact , type ContactWithoutId } from './contact' ;
44import { URL } from './config' ;
5- import { injectInfiniteQuery , injectMutation , injectQuery , injectQueryClient } from '@tanstack/angular-query-experimental' ;
65import { lastValueFrom , tap } from 'rxjs' ;
6+ import { Store } from '@ngrx/store' ;
7+ import { selectAllContact , selectContactStorePendingStatus } from './store/contact/contact.selectors' ;
8+ import { setContactEntitiesFromApi } from './store/contact/contact.actions' ;
79
810interface ContactResponse {
911 data : Contact [ ] ;
@@ -18,152 +20,27 @@ interface ContactResponse {
1820export class BackEndService {
1921 /// Tanstack query usage
2022 private readonly http = inject ( HttpClient ) ;
21- public queryClient = injectQueryClient ( ) ;
2223 public currentId = signal ( '1' ) ;
2324 public filter = signal ( '' ) ;
2425 public currentStart = signal ( 0 ) ;
2526 public currentLimit = signal ( 5 ) ;
2627 public currentPage = signal ( 1 ) ;
2728
28- public contact = injectQuery ( ( ) => ( {
29- queryKey : [ 'contact' , this . currentId ( ) ] ,
30- queryFn : ( ) => {
31- // console.log('in getContact$ with id', id);
32- return lastValueFrom ( this . http . get < Contact > ( `${ URL } /${ this . currentId ( ) } ` ) ) ;
33- } ,
34- staleTime : 60 * 1000 , // 1 minute
35- initialData : ( ) => this . queryClient . getQueryData < Contact [ ] | undefined > ( [ 'contacts' , '' ] ) ?. find ( ( contact ) => contact . id === this . currentId ( ) ) ,
36- initialDataUpdatedAt : ( ) => this . queryClient . getQueryState ( [ 'contacts' , '' ] ) ?. dataUpdatedAt
37- } ) ) ;
29+ // store solution
30+ public readonly store = inject ( Store ) ;
3831
39- public contacts = injectQuery ( ( ) => ( {
40- queryKey : [ 'contacts' , this . filter ( ) ] ,
41- queryFn : ( ) => {
42- // console.log('in getContact$ with id', id);
43- return lastValueFrom ( this . http . get < Contact [ ] > ( `${ URL } ?q=${ this . filter ( ) } ` ) ) ;
44- } ,
45- staleTime : 60 * 1000 // 1 min
46- } ) ) ;
32+ public allContact = this . store . select ( selectAllContact ) ;
4733
48- public mutationSave = injectMutation ( ( ) => ( {
49- mutationFn : ( contact : Contact ) => {
50- // console.log('Save mutate contact:', contact);
51- return lastValueFrom ( this . saveFn ( contact ) ) ;
52- } ,
53- onMutate : async ( contact ) => {
54- // cancel potential queries
55- await this . queryClient . cancelQueries ( { queryKey : [ 'contacts' ] } ) ;
56-
57-
58- const savedCache = this . queryClient . getQueryData ( [ 'contacts' , '' ] ) ;
59- // console.log('savedCache', savedCache);
60- this . queryClient . setQueryData ( [ 'contacts' , '' ] , ( contacts : Contact [ ] ) => {
61- if ( contact . id ) {
62- return contacts . map ( ( contactCache ) =>
63- contactCache . id === contact . id ? contact : contactCache
64- ) ;
65- }
66- // optimistic update
67- return contacts . concat ( { ...contact , id : Math . random ( ) . toString ( ) } ) ;
68- } ) ;
69- return ( ) => {
70- this . queryClient . setQueryData ( [ 'contacts' , '' ] , savedCache ) ;
71- } ;
72- } ,
73- onSuccess : ( data : Contact , contact : Contact , restoreCache : ( ) => void ) => {
74- // Should we update the cache of a "contact" here ?
75- restoreCache ( ) ;
76- this . queryClient . setQueryData ( [ 'contact' , data . id ] , data ) ;
77- this . queryClient . setQueryData ( [ 'contacts' , '' ] , ( contactsCache : Contact [ ] ) => {
78- if ( contact . id ) {
79- return contactsCache . map ( ( contactCache ) =>
80- contactCache . id === contact . id ? contact : contactCache
81- ) ;
82- }
83- return contactsCache . concat ( data ) ;
84- } ) ;
85- } ,
86- onError : async ( _error , variables , context ) => {
87- context ?.( ) ;
88- await this . settledFn ( variables . id ) ;
89- }
90- } ) ) ;
91-
92- public mutationDelete = injectMutation ( ( ) => ( {
93- mutationFn : ( id : string ) => {
94- // console.log('Save mutate contact:', contact);
95- return lastValueFrom ( this . removeFn ( id ) ) ;
96- } ,
97- onMutate : ( id : string ) => {
98- const savedCache = this . queryClient . getQueryData < Contact [ ] > ( [ 'contacts' , '' ] ) ;
99- // console.log('savedCache', savedCache);
100- this . queryClient . setQueryData ( [ 'contacts' , '' ] , ( contacts : Contact [ ] ) =>
101- // optimistic update
102- contacts . filter ( ( contactCached ) => contactCached . id !== id )
103- ) ;
104- return ( ) => {
105- this . queryClient . setQueryData ( [ 'contacts' , '' ] , savedCache ) ;
106- } ;
107- } ,
108- onError : async ( _error , variables , context ) => {
109- context ?.( ) ;
110- await this . settledFn ( variables ) ;
111- } ,
112- onSettled : ( _data : Contact | undefined , _error , variables , _context ) => this . settledFn ( variables )
113- } ) ) ;
114-
115- public infiniteQuery = injectInfiniteQuery ( ( ) => ( {
116- queryKey : [ 'contacts' ] ,
117- queryFn : ( { pageParam } ) => {
118- return lastValueFrom ( this . getInfiniteContacts ( pageParam ) ) ;
119- } ,
120- initialPageParam : this . currentPage ( ) ,
121- getPreviousPageParam : ( firstPage ) => firstPage . prev ?? undefined ,
122- getNextPageParam : ( lastPage ) => lastPage . next ?? undefined
123- } ) ) ;
124-
125-
126- public nextButtonDisabled = computed (
127- ( ) => ! this . #hasNextPage( ) || this . #isFetchingNextPage( )
128- ) ;
129- public nextButtonText = computed ( ( ) =>
130- this . #isFetchingNextPage( )
131- ? 'Loading more...'
132- : this . #hasNextPage( )
133- ? 'Load newer'
134- : 'Nothing more to load'
135- ) ;
136- public previousButtonDisabled = computed (
137- ( ) => ! this . #hasPreviousPage( ) || this . #isFetchingNextPage( )
138- ) ;
139- public previousButtonText = computed ( ( ) =>
140- this . #isFetchingPreviousPage( )
141- ? 'Loading more...'
142- : this . #hasPreviousPage( )
143- ? 'Load Older'
144- : 'Nothing more to load'
145- ) ;
146-
147- readonly #hasPreviousPage = this . infiniteQuery . hasPreviousPage ;
148- readonly #hasNextPage = this . infiniteQuery . hasNextPage ;
149- readonly #isFetchingPreviousPage = this . infiniteQuery . isFetchingPreviousPage ;
150- readonly #isFetchingNextPage = this . infiniteQuery . isFetchingNextPage ;
34+ public isPending = this . store . select ( selectContactStorePendingStatus ) ;
15135
36+ public isFailing = this . store . select ( selectContactStorePendingStatus ) ;
15237
15338 constructor ( ) {
154- effect ( async ( ) => { if ( ! this . nextButtonDisabled ( ) ) {
155- await this . fetchNextPage ( ) ;
156- } } ) ;
157- }
158-
159- public async settledFn ( contactId : string | undefined ) {
160- await this . queryClient . invalidateQueries ( { queryKey : [ 'contacts' ] } ) ;
161- if ( contactId ) {
162- await this . queryClient . invalidateQueries ( { queryKey : [ 'contact' , contactId ] } ) ;
163- }
39+ // store solution
40+ this . store . dispatch ( setContactEntitiesFromApi ( { call : lastValueFrom ( this . http . get < Contact [ ] > ( `${ URL } ?q=` ) ) } ) ) ;
16441 }
16542
166- public saveFn ( contact : Contact ) {
43+ public saveFn ( contact : ContactWithoutId ) {
16744 if ( contact . id ) {
16845 return this . http . put < Contact > ( `${ URL } /${ contact . id } ` , contact ) ;
16946 }
@@ -177,12 +54,4 @@ export class BackEndService {
17754 public getInfiniteContacts ( pageParam : number ) {
17855 return this . http . get < ContactResponse > ( `${ URL } ?_page=${ pageParam . toString ( ) } &_per_page=${ this . currentLimit ( ) . toString ( ) } ` ) . pipe ( tap ( ( ) => this . currentPage . set ( pageParam ) ) ) ;
17956 }
180-
181- public async fetchNextPage ( ) {
182- // Do nothing if already fetching
183- if ( this . infiniteQuery . isFetching ( ) ) {
184- return ;
185- }
186- await this . infiniteQuery . fetchNextPage ( ) ;
187- }
18857}
0 commit comments