#RxJS return sync (local data) and async (http fetch) data together.

21 messages · Page 1 of 1 (latest)

steady pier
#

I've been trying to learn RxJS and transform some of the stuff I did before to RxJS. I also have some questions overall about code quality and best practices.

Suppose I have these type definitions: image1 (char limit 😢 )
Here I wanted to ask which is considered better practice for limiting inputs to functions: type or const enum, both have intellisense but I find type solution better since I have
('name1', 'name2') instead of (CodebookNames.name1, CodebookNames.name2), less verbose.

Currently my sevice is this: image2 and image3
I have serval questions:

  1. I suppose for local values i don't even need ReplaySubject i could do everything with only last variable. Would this be considered good?
  2. I don't know how to make typescript believe I always either have codebooksHere$, codebooksFetching$ or both (since i have minimum of 1 parameter input). I need to use ! operator on my returns.
  3. I tried a lot of operators but got nothing working except combineLatestWith, a lot of them say the will bi depracated in v8. What I want to do is that if I have both local and fetching codebooks first send only locals, but after fetching ones are fetched I want to send a new combined object of both local and fetched ones.
#

As text, types:

export type Codebooks = {
  [index: string]: CodebookData[]
}

export type CodebookData = {
  id: string,
  name: string
}

export type CodebookNames = 'names1'
                            | 'name2'
                            | 'name3'
#

Service, part1:

@Injectable({
  providedIn: 'root',
})
export class CodebooksService {

  private codebooks$: ReplaySubject<Codebooks> = new ReplaySubject<Codebooks>(1);
  private last: Codebooks = {};

  constructor(
    private api: ApiService,
  ) {
  }

  public getCodebooks(codebookName: CodebookNames, ...codebookNames: CodebookNames[]): Observable<Codebooks>
  public getCodebooks(...codebookNames: CodebookNames[]): Observable<Codebooks> {

    const codebookNamesHere = codebookNames.filter(codebookName => this.last[codebookName]) as string[];
    const codebookNamesToFetch = codebookNames.filter(codebookName => !this.last[codebookName]) as string[];

    let codebooksHere$: Observable<Codebooks>;
    if (codebookNamesHere.length > 0) {
      codebooksHere$ = this.codebooks$.asObservable()
                           .pipe(
                             map(codebooks =>
                               Object.keys(codebooks)
                                     .filter(key => codebookNamesHere.includes(key))
                                     .reduce((obj, key) => {
                                       return {
                                         ...obj,
                                         [key]: codebooks[key],
                                       };
                                     }, {}) as Codebooks,
                             ),
                           );
    }

    let codebooksFetching$: Observable<Codebooks>;
    if (codebookNamesToFetch.length > 0) {
      codebooksFetching$ = this.api.get<Codebooks>(`/codebooks/${codebookNamesToFetch}`)
                               .pipe(
                                 tap(codebooks => {
                                   this.last = Object.assign(this.last, codebooks);
                                   this.codebooks$.next(
                                     this.last,
                                   );
                                 }),
                               );```
#

Service pat2:


    if (codebookNamesHere.length > 0 && codebookNamesToFetch.length > 0) {
      return codebooksHere$!.pipe(
        combineLatestWith(codebooksFetching$!),
        map((test: [Codebooks, Codebooks]) => Object.assign(test[0], test[1])),
      );
    } else if (codebookNamesHere.length > 0) {
      return codebooksHere$!;
    } else {
      return codebooksFetching$!;
    }
  }
}

stone rose
#

I'd go with type rather than enum, which also skips TS having to generate code

const MyEnum = ['name1', 'name2'] as const;
type MyEnum = typeof MyEnum[number];
#

Also I'm not sure what you want to do with that "here" and "fetch"

#

You have a codebook only in the browser, and a list of codebooks from an API, and want to merge both?

steady pier
#

Yeah, basically i start with 0 codebooks in the browser, each time i fetch some i want to cache them for the next time.
In case someone requests codebooks part of which i have on browser i want to send that part immediately and full request when left over part is fetched.

stone rose
#

So you'd want some Map<CodebookId, Observable<Codebook>>?

#

But it "returns" the cached one straight away while fetching an update?

#

Although it seems to be lists and not individual codebooks

steady pier
#

For example.

// i start with 0 codebooks in cache
codebooks$ = this.codebooksService.getCodebooks('codebook1');
// i recieve observable of codebook1 which is fetching
codebooks2$ = this.codebooksService.getCodebooks('codebook1', 'codebook2');
// Now i want this observable to emit 2 times.
// First time it will be codebook1 since that is already cached
// Second it will be object of { codebook1: data, codebook2: data } 

steady pier
#

I mean i could fetch each codebook individually but that would mean more calls to the backend.

stone rose
#
const namesToFetch = new Subject<string>();
const codebooks$ = namesToFetch.asObservable().pipe(
  mergeMap(names => this.api.get<Codebooks>(`/codebooks/${names}`)),
  // accumulate the books
  scan(
    (cachedBooks, receivedBooks) => [...cachedBooks, ...receivedBooks],
    []
  ),
  // start with an empty list
  startWith([]),
  // and make it a "singleton"
  shareReplay(1)
);
#

Maybe

#

(probably with a smarter thing than copying the arrays to avoid duplicates)

#

scan is like reduce but emits on every iteration

steady pier
#

I can see the idea of it, could try something with that.

stone rose
#

Could also take a peak at ngrx, but that'd be more than "just a cache"

steady pier
#

Yeah, I want to learn RxJS for now only. Don't want to complicate till I understand the limits.