Search code examples
angulartypescriptrxjs6

How to compare two cold Observable's sources (not the potential streams they emit)


Scenario: I am using publishReplay to cache and replay certain Http requests within an Angular application (using publishReplay over shareReplay due to some bugs that have since been fixed in rxjs).

My problem is that I want to force the cached observable source to update itself and create a new cached observable with publishReplay if I detect a URL parameter change. I thought perhaps this could be accomplished by interrogating/comparing the newly proposed observable with the existing cached observable's source, but I have been unsuccessful in achieving this.

What I am looking for: A way to either interrogate an observable's source so I can compare it to the source of another observable to see if they are identical (url in this case), or some other means of detecting the url on an Http Observable changed with publishReplay. Furthermore I am attempting to abstract this logic away into a caching mechanism so I don't have to sprinkle code throughout the apps that detects if the parameter changes.

Here is a very naive example of what I'd like to do that isn't working, the input source parameter is the new Observable which contains a handle to an http request, and the origSource is the previous observable which contains the same (though potentially with a different url parameter that publishReplay doesn't seem to acknowledge).

  // determine if key exists and source is un-modified (i.e params did not change)
protected isEntreeValid<T>(key: ICacheKeyConstantsModel, source: Observable<T>): boolean {
    if (!this.has(key)) { return false; }
    if (this.has(key)) {
        let origSource = this.cache.registry.get(key).source; // observable previously cached used to resubscribe to later and returned cached publishReplay response, or can be tickled to make the original http request again effectively busting the cache.
        console.log('source: ', source);
        console.log('are they equal', source.source === origSource.source); //always returns false but assume need to check some inner value of observable anyways and do a fuzzy comparison.
        if (source.source !== origSource.source) { return false; }
    }

    return true;
}

Above Method: The input 'source' param in above method is just a pointer to an http.get method and stored in a Map in key/value pair fashion: i.e. Map so that we can marshal their caching state and other attributes in a centralized way more easily. Basically it is here that if a 'key' already exists I'd like to evaluate 'source' to see if parameters on the URL changed so if they have we can bust the cache, alternatively if they haven't we'll just return the publishReplay result.

Note: I do not intend/want to connect to the Observable in this case, I am interested only in comparing the source of these Observables, not the potential streams they will emit.

Update: Found what I was looking for, though not sure why it is WAAAY down there. Is there a slick way of iterating down to this, seems consistent structurally with Observables and not an artifact of something weird I did when creating these ones. Seems odd indeed that this very basic info would be so far down in there (note url has been obfuscated and this example didn't have params):

Property location is: Observable.value.source.source.source.source.source

Structure of object at this location is:

{

value: HttpRequest

body: null

headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)}

method: "GET"

params: HttpParams {updates: null, cloneFrom: null, encoder: HttpUrlEncodingCodec, map: Map(0)}

reportProgress: false

responseType: "json"

url: "https://my-dev-server.com/primary/HealthCareApp/Patients"

urlWithParams: "https://my-dev-server.com/primary/HealthCareApp/Patients"

}

Question: Any clean/reliably ways to flatten, or drill-down to the inner most Observable without subscribing?

Update 2:

For now I'm using a method like the one below to extract the innerObservable, there are probably better ways but here's what we're rocking with for the time being:

// retrieve inner meta-data from cold observable for interrogation without subscribing
private extractInnerObservable<T>(source: Observable<T>): Observable<T> {
    let target = observableOf(source)['value'];
    while (target['source']) {
        target = target['source'];
    }

    return target;
}

Resulting Method:

enter image description here

Conclusion: Still open to exploring other options for sure, but I’ve updated this question multiple times to share where I’m at as am at least in a state now where it addresses the original problem (though certainly not in the most elegant fashion). However Incase someone else is struggling with something similar thought there would be value in sharing the result of where some of this madness ended up.


Solution

  • I'd argue to invert your logic here. if there's some param coming through that you want to change the cached request on, then use operators to do it...

    private paramSource = new Subject<string>(); // whatever type is appropriate
    cached$ = this.paramSource.pipe(
      distinctUntilChanged(), // could add some custom compare function in here
      switchMap(param => this.makeRequest(param)),
      shareReplay(1) // whatever you wana do for caching
    );
    
    // call this whenever you might've wanted to run the observable inspection
    updateParam(param: string) {
      this.paramSource.next(param);
    }
    

    not positive if this achieves your goals directly, as I'm alittle unclear on what you're after, but hopefully this is close.