How to Retain Previous Values During Angular Resource Refetch
Have you ever noticed a brief flicker or a flash of empty data on your Angular UI when a resource is refetching? It's a common scenario: your application initiates a data fetch, and for a moment, the old data disappears before the new data arrives, leaving a less-than-ideal user experience.
While it makes sense for a resource to report undefined or empty during a refetch—after all, it genuinely doesn't have the new value yet—many applications benefit from simply continuing to display the last known good value until the fresh data is ready. This prevents that jarring visual discontinuity.
The Problem with UI Flickers
Imagine you're viewing a list of products. You apply a filter, triggering a new fetch. Without intervention, your product list might momentarily vanish, only to reappear with the filtered results. This "data disappearing act" can be disruptive and make your application feel less polished.
The Elegant Solution: linkedSignal
Fortunately, there's a neat workaround using a utility often found in modern Angular state patterns, like linkedSignal (credits to a helpful discussion on GitHub). This pattern allows you to create a derived signal that intelligently prioritizes new data while gracefully falling back to previous data during a refetch.
What it Does
This approach essentially creates a "view" signal (productsForView in this example) that monitors your actual resource signal (productsValue). When the resource is actively refetching and its value might be undefined or null, productsForView will continue to emit the last successfully loaded value until the new data is available.
Why it's Useful
- Improved User Experience: Eliminates flickering or flashing of empty states during data refetches.
- Smoother Transitions: Your UI remains stable and populated, providing a seamless experience.
- Simple Implementation: Integrates cleanly into your existing state management.
How to Use It
Here’s how you can integrate withLinkedState and linkedSignal into your Angular store or component's state definition:
// Assume you're fetching products reactively using a resource
withResource((store) => ({
products: store.productsApi.productsResource(store.filters), // products.value will be store.productsValue
// ... other resources
})),
// Now, make sure the previous value is retained till the new one comes in
withLinkedState((store) => ({
productsForView: linkedSignal<Product[]>({ // Specify the type of your data
source: store.productsValue, // The actual signal holding your fetched product array
computation: (source, previous) => {
// If 'source' (new data) has a value, use it.
// Otherwise, if 'previous' (old data) has a value, use its .value.
// Otherwise, default to an empty array.
return source ?? previous?.value ?? [];
},
}),
})),
Let's break down the linkedSignal part:
source: store.productsValue: This is the actual signal from your resource (store.products.value) that holds the data currently being fetched. WhenproductsResourceis refetching,store.productsValuemight temporarily beundefinedornull.computation: (source, previous) => { ... }: This is the core logic.source ?? ...: If thesourcesignal currently has a value (meaning the new data has arrived), that value is returned immediately.previous?.value ?? ...: Ifsourceisnullorundefined(indicating an ongoing refetch or initial empty state), thecomputationfalls back toprevious.previoushere refers to the last successfully emitted value of theproductsForViewsignal itself. We access its.valueproperty.[]: As a final fallback, if neithersourcenorprevious?.valuehas a value (e.g., on initial load when no data has ever been fetched), it defaults to an empty array.
By employing linkedSignal with this simple computation, you ensure that your UI always has something to show, gracefully transitioning from old data to new data without any awkward blanks. It's a small change that makes a big difference in application polish.
Full code (with firebase, stripe backend) on my shop: https://zoaibkhan.com/shop/angular-ecommerce?utm_source=linked-state-snippet
