0

I have an Angular app with a persistent store and a list of keys for storage items. I want to load the values for the known keys from the persistent store into memory asynchronously at app start, so that they can be accessed synchronously while the app is running. So I created an Observable, which iterates over all elements of the key list and have a pipe attached, which should get the value for each element and hands over the combination of key and value, so I can store them in a cache. The case that a value can't be found in the persistent storage should be catched. This unfortunatly works only for one of the keys. Then the outer Observable completes unexpectedly.

By looking at the log, I can see, that the outer Observable iterates correctly over the three elements of the key list and runs the switchMap(). But then only one value is pulled out of the persistent storage and the from-Observable completes after the localStorageService.get()-Observable completes for the first element.

The logs shows this:

  • from() $.next => (2) ["authorizationDataIdToken", 1]
  • switchMap(): key: authorizationDataIdToken
  • from() $.next => (2) ["_isAuthorized", 0]
  • switchMap(): key: _isAuthorized
  • from() $.next => (2) ["userData", 0]
  • switchMap(): key: userData
  • from() $.complete
  • localStorageService.get(userData) $.next => [object Object]
  • from() - after switchMap $.next => (2) ["userData", "[object Object]"]
  • OIDC Storage: Cache key: userData, value: [object Object]
  • localStorageService.get(userData) $.complete
  • from() - after switchMap $.complete
  • OIDC Storage: ready for use.

Here is the code for the load-into-cache-function:

private prepareForUsage() {
    // Take each element from the securely-stored-keys-list,
    // get their values from the secured storage and put them into the cache.
    from(Object.entries(this.keysStoredSecurely)).pipe(
        log('from()'),
        // At this point, we got the key and want to load the value from the local storage.
        // But we want to have both, key and value afterwards.
        switchMap(([key, protectionLevel]) => {
            console.log('switchMap(): key: ', key);
            // Here we try to fetch the value for the key via local storage service.
            return this.localStorageService.get(key, true).pipe(
                log(`localStorageService.get(${key})`),
                // Because the item we want to access might not exist yet, we could get an error, that we want to catch.
                catchError((error, _) => {
                    if (isDevMode()) { console.warn(`${error}, key: ${key}`); }
                    return of(undefined);
                }),
                // Here we have the value for the key and we want to combine both and return it.
                map(value => [key, value])
            );
        }),
        log('from() - after switchMap'),
    ).subscribe({
        // Put each item in the cache.
        next: ([key, value]) => {
            if (key === undefined || value === undefined) {
                if (isDevMode()) { console.log(`OIDC Storage: Key or value missing. key: ${key}, value: ${value}`); }
            } else {
                if (isDevMode()) { console.log(`OIDC Storage: Cache key: ${key}, value: ${value}`); }
                sessionStorage.setItem(key, value);
            }
        },
        error: (error) => {
            if (isDevMode()) { console.error(`OIDC Storage: ${error}`); }
        },
        // Declare the storage as ready for usage.
        complete: () => {
            if (isDevMode()) { console.log('OIDC Storage: ready for use.'); }
            this.isReadyForUsage.next(true);
        }
    });
}

This is the signature of the get() function of the local storage service:

get(key: string, encrypted: boolean = false): Observable<string>

I expect the call of the get() function of the local storage service to be executed for every element the from-Observable emits, but I can see it only called once and have no clue, what I did wrong.

tosi
  • 787
  • 1
  • 7
  • 12

1 Answers1

0

Ok, I found it out. The problem was, that the elements emitted by the from-Observable came in very fast and the inner Observable of switchMap does only handle the last incoming element and unsubscribes from the ones before. Changing the switchMap() to mergeMap() did the trick.

tosi
  • 787
  • 1
  • 7
  • 12