thk
thk

Reputation: 69

Rxjs init resource once with hot observable

I have a service setup with this code below :

@Injectable()
export class StaticResourcesService {

private resourcesTree$: Observable<any>;

getSpecificLabel(resourceName: string, type: string): Observable<string> {
    return this.getResources().map(resources => {
      return jp.value(resources, `$.${resourceName}[?(@.type=="${type}")].label`);
    });
  }

private getResources(): Observable<any> {
    if (this.resourcesTree$ != null) {
      return this.resourcesTree$;
    }
    let source = this.getResourcesFilePath() // get obs<string> with a file path
      .mergeMap(path => {
        console.log(`static-resources path: ${path}`);
        return this.http.get(path).map(res => res.json()); // get json obj
      }).do(res => {
        this.resourcesTree$ = Observable.of(res);
      });
    return source;
  }

a custom angular pipe with a transform method like this :

transform(resourceType: string, resourceName: string): Observable<string> {
    return this.staticResourcesService.getSpecificLabel(resourceName, resourceType);
  }

and I use this custom pipe in a simple html page:

<div *ngFor="let item of items">{{ item?.status | staticResource:'MY_RES' | async }}</div>

When I load the page for the first time, the custom pipe will process 4 times (the ngFor loops 4x) and will never get its source data from resourcesTree$. I understand that its a normal behavior as the subscriptions will listen at the same time and the do will be performed 4x. But what I want is an optimized way with accessing the data only one time and then take advantage of resourcesTree$ the next 3 times. I tried to makes it "hot" by using the share() method on the source. But I can't make it work properly.

Can somebody tell me how to achieve this please ? Maybe Im all wrong and Im really open to any suggestions here.

Thank you

Upvotes: 0

Views: 566

Answers (1)

Richard Matsen
Richard Matsen

Reputation: 23483

The usual pattern for an async service is to make the shared resource a Subject() or ReplaySubject(). That way, you just hook up subscribers and they wait nicely for data to arrive.

In your example, it's slicing up the resource, so that's a map operator (as you have already). Also some way to signal the fetch has started (don't care if it's finished yet, since it's observable).

Since getResources() is no longer subscribed externally, you'll need to replace .do() with .subscribe().

@Injectable()
export class StaticResourcesService {

private resourcesTree$ = new ReplaySubject(1);
private status = 'unloaded';

getSpecificLabel(resourceName: string, type: string): Observable<string> {
  if( this.status === 'unloaded') {
    this.status = 'loading';
    getResources();   
  }
  return resourcesTree$.map(resources => {
    return jp.value(resources, `$.${resourceName}[?(@.type=="${type}")].label`);
  });
}

private getResources(): Observable<any> {
  this.getResourcesFilePath() // get obs<string> with a file path
    .mergeMap(path => {
      console.log(`static-resources path: ${path}`);
      return this.http.get(path).map(res => res.json()); // get json obj
    })
    .subscribe(res => {
        this.resourcesTree$.next(res);
        this.status = 'loaded';
      });
  }

Upvotes: 1

Related Questions