[Rxjs] Rxjs store for angular (rx-ng-store)2

kelly woo
4 min readFeb 17, 2022
Photo by Mick Haupt on Unsplash

If you haven’t read the article, it would be helpful to read the first one, but not necessary.

So I made a lot of changes from last article.
Yes, I’m admitting that the first one was poorly designed, but it is critical step to practice, failing, it was only draft on the big white paper and I’d like to keep on drawing.

Here’s what I added from the last article.

  1. split core and angular part (can try on react later with it.)
  2. separate RxStore(static and simple api call) and RxQuery(refetch & cache)
  3. add RetryStrategy (to both)
  4. add RefetchStrategy(to RxQuery)
  5. add CacheStrategy(to RxQuery)
  6. create separate class for State(it’s for cache)
  7. add rxNgSuspense directive

And believe me there were a lot of steps to decide and I’ll explain why I chose that.

RetryStrategy

/**
*
@description retry times for error
**/
retry?: number;
/**
*
@description delay time for retry
**/
retryDelay?: number;

Retry is how many times tries if error has been returned.
The default is 2, which means 2 times more tries and each retry applies delay time provided by retryDelay.

RefetchStrategy

/**
*
@description refetch on reconnect(if the staleModeDuration has passed)
**/
refetchOnReconnect?: boolean;
/**
*
@description refetch on visibility changes(if the staleModeDuration has passed)
**/
refetchOnEmerge?: boolean;
/**
*
@description interval for refetch, if query performs before next routine, it reset the timer
*
default is 1day. and 0 for disable it
**/
refetchInterval?: number;
/**
*
@description automatically stops refetch on staleMode(window visibility hidden & offline)
* with true, it will refetch in staleMode
*
default is false
**/
refetchOnStaleMode?: boolean;

/**
*
@description refetch or reconnect debounce time
**/
staleModeDuration?: number;

The first question.
When are you gonna refetch the api?
: reconnect, window focus, and probably interval refetch.

2.Then it comes with these questions,
- Should it be for every time each event happens?
- Should refetch be executed under the background mode(offline or inactive window, it will be changed to staleMode)?

I chose

  • No, that’s why backgroundStaleTime added, if latest off to on duration is bigger than backgroundStaleTime.
  • No, but some might want this feature. so provide refetchOnStaleMode option.
  • But only when the interval refetch failed on staleMode, it might be better to refetch.

CacheStrategy

To apply caching, need to define the target and how to distinguish which cache keeps and which cache updated to.
cacheKey is essential For this.

/**
*
@description to distinguish unique cache, it takes param sent to fetch as argument
**/
paramToCachingKey?: (p: any) => any;

/**
*
@description how many extra cache(distinguished by cachekey) can be saved. default is 0(current cacheKey only).
**/
caching?: number;

/**
*
@description keep the cache after destroying and can be used for next time
**/
keepAlive?: boolean;

So there’s 3 ways to provide cachingKey.
If caching is 0, we don’t need to check. so key is unified.
paramToCachingKey function can provide caching key, or before that param has rxQueryCachingKey, it will be used as unique cachingKey.
And the last is param itself.
All key will be checked with 2depths of shallowEqual

getCacheKey(param?: any) {
if (caching === 0) {
return INIT_CACHE_KEY;
}

if (
param &&
typeof param === 'object' &&
Object.prototype.hasOwnProperty.call(param, 'rxQueryCachingKey')
) {
return param.rxQueryCachingKey;
}

if (this.paramToCachingKey) {
return this.paramToCachingKey(param);
}
return param;
}

By cachingKey RxCache is allocated and it only keeps previous cache.
And RxState manage of RxCache.

Also here are some questions.
Who should own loading responsibility, cache itself or state.
I put loading with cache since all loading status comes with state update but am still sleeping on it.

rxNgSuspense

You might be familiar with suspense in react.
It is good interface for contents with loading and error.
So the state interface has been updated like the following.

export type RxQueryStatus<A> = {
ts: number; // to check the last updated
data: A; // api or async operation response
loading: boolean; // is it loading-fetching now?
error: Error | null; // has error?
untrustedData: boolean // in loading or last one was error?
};

And we can use it with async pipe.

<ng-template 
[rxNgSuspense]="status$ | async"
[errorTemplate]="errorTemplate"
[loadingTemplate]="loadingTemplate" let-data>
<ul>
<li *ngFor="let item of data">
<a (click)="showDetail(item.id)">{{ item.title }}</a>
</li>
</ul>
</ng-template>
<ng-template #loadingTemplate>...loading</ng-template>
<ng-template #errorTemplate>...error</ng-template>
//
status$ = this.rxStore.status('someItemKey');

If you’d like to change template order by data, templateSort can support this.

defaultTemplateSort(status: RxQueryStatus<any>){
const { loading, data, error, untrustedData } = status;
if (untrustedData) {
if (error) {
return 'error';
}
if (loading) {
return 'loading';
}

return data ? 'content' : 'null';
}

if (data) {
return 'content';
}
return 'null';
}
<ng-template [rxNgSuspense]="..."
[templateSort]="defaultTemplateSort">
...
</ng-template>

So these are what I’ve don’t this so far.
Still there are many things to take into account, like

  • it does not work with hot module reload in dev
  • still has a loose connection between param & state when you use mutate(not sure the mutate should update this one or last one, or later one…should I have to get the key, or just leave it to user.)
  • has to check performance & memory
  • needs unit testing
  • needs checking the paths of what can happen when the existing store destroyed and called

I think I’ve done 50% so far, since well begun is half done and I just finished starting chapter(I guess, and I hope so).

I would really appreciate your feedback, feedforward and any critics.
(published library is a bit different from this article since it is not upToDate.)

Thank you for reading :)

--

--