Angular Challenges #30 Interoperability Rxjs/Signal Alternative Solution using NgRx Signal Store.
- Angular
- Angular CLI version 17.0.7.
- NgRx Signal Store
- Tailwind
- Flickr API
- Since NgRx Signals Store has been officially released, I decided to try it out. Quite frankly, it is a little overwhelming. Since everything hinges on the
signalStore
method, you can't easily debug and piecemeal a solution. There is limited feedback. - I started my conversion by following the NgRx docs.
- To make further progress, I had to remove some functionality, such as the disabling of the nextPage button and the use of local storage. I also had to remove some TypeScript restrictions. I removed the input and gave an initial value to
search
to ensure an API request would happen. - The API request was executed, but I couldn't see it in the template. With signals, sub-components seem necessary. You can't have a form and render the response data in the same component with signals?
- To get the input to work, I needed to add a change handler and bind that to the
Output
insearch-box.component.ts
. - This Angular Challenge PR was a critical resource in troubleshooting this app. Looking at the PR, Marko segmented the code much better than I have done. I kept the initial state object rather than breaking state into multiple pieces.
- Now, I can see why. I don't think you can get access to
$update
when you add a store and a service towithMethods
. - Unfortunately, the PR has outdated syntax, as the signal store API seems to have changed slightly. Now, I think
$update
is no more. When I tried to reuse Marko'swithStorageSync
,$update
was underlined and not found by VS Code. I relied onpatchState
to update the store's state. - For the pagination, I could update the page value, but this wouldn't trigger another API request. If the input doesn't change, the API request will not be triggered.
- The
loadSearch
method usesdistinctUntilChanged
. This might prevent theloadSearch
method from being called again assearch
has not changed. - Ultimately, the quickest solution was to duplicate
loadSearch
. I called the new methodnewPageSearch
and called it inside thenextPage
andprevPage
methods. InsidenewPageSearch
, I removed thedistinctUntilChanged
and thedebounceTime
calls. So there is a slight benefit from using the new method, as the API request can happen as soon as the buttons are pressed. - Marko used a
signalStoreFeature
calledwithStorageSync
to handle local storage. I tried to import it, but the signal store API seems to have changed. There are TypeScript issues and$update
can't be used to update state. withStorageSync
saves the whole state. But since I have not segmented state, this doesn't work for this app. Only thesearch
,page
, andpages
need to be saved.- Saving
pages
might not be necessary. There seem to be issues with thepages
count and it seems to change more often than it should. Maybe theflickr
API is so active that the page count would change often for certain keywords. - So options are to try to recreate something similar or maybe just update the
onInit
hook insidewithHooks
. You can't have multipleonInit
hooks. withHooks
is limited with only 2 methodsonInit
andonDestroy
.- Ultimately, I added a similar
signalStoreFeature
calledlocalStorageSync
. I used object destructuring andpatchState
to update local state with values from local storage. newPageSearch
doesn't check local storage.- The challenge code includes an API key for
flickr
. Although it is already exposed, I shouldn't have included it in my repo. So I removed it and added an environments folder with aapiKey
variable. Now, you can freely change API keys and not worry about including them ingit
. - I used
git filter branch
to rewrite history and remove the file fromgit
. When I added back the service, I changed the file name tophoto.service
instead ofphotos.service
. - The signal store uses
rxMethod
and RxJs for asynchronous tasks. If you were to do this challenge normally, all asynchronous tasks should be performed with RxJs and all synchronous tasks should use signals.
- Typescript improvements
- Pagination implementation is not ideal. I used a repetitive solution.
- Testing -> I'd imagine this will be difficult (lack of documentation). See Github for updates.
- Page number is saved -> if you search for something that has less page results than the page you are currently on, you will see no images and need to use the back arrow to see the results.
{ loading: true, page: 1 }
works to solve the problem, but if a user refreshes the page, the page will be lost. I potentially fixed the problem with a ternary that sets the local storage page value inside theloadSearch
method. I initially added@ts-ignore
above the call. I was able to correct the error by converting the local storage item to a number. All local storage items are stored asstring
key-value pairs. - This lead me to discovering that the empty results conditional logic was wrong. I used
@empty
but the condition was inside anotherif
statement so the empty condition was never run. I converted to useelse
to fix this problem. - Using conditional logic inside a
tap
is less than ideal. This could be very difficult to test. Without a total rework, this might be the best solution possible.
To clone and run this application, you'll need Git and Node.js (which comes with npm) installed on your computer. From your command line:
# Clone this repository
$ git clone https://github.com/jdegand/interop-rxjs-signal.git
# Install dependencies
$ npm install
# Need to add / update environment.development.ts file with an apiKey from flickr
# Run the app and navigate to localhost:4200
$ npm start
- transform.tools - json to typescript
- NgRx Docs - rxjs integration
- Angular Training - 3 ways to update Angular Signals
- Github - (Closed) RFC: NgRx SignalStore
- Angular Addicts - ngrx component store to signal store
- Dev.to - handling pagination with ngrx component stores
- Angular Architects - the new ngrx signal store for angular 2-1-flavors
- Stack Overflow - local storage checking if a key exists (use null)