Rishav Tandon
Rishav Tandon

Reputation: 115

Read value of observable with subscribing to it

I have an observable like

imageOptions$: Observable<BoundImagesToProject[]> = this.imagesService
.getBoundImages({ projectId: this.projectId })
.pipe(map((images) => (images.data)));

and in the template I use it like

<div class="form-field input-group">
    <label for="image">Image</label>
    <mat-select id="image" class="select--full-width" placeholder="Image" formControlName="image">
      <mat-option *ngFor="let image of imageOptions$ | async" [value]="image.imageId">
        {{ image.name }}
      </mat-option>
    </mat-select>
    <mat-error *ngIf="createVmForm.get('image').getError('required') && createVmForm.get('image').dirty"
      >Field is required</mat-error
    >
  </div>

Now I want to use the imagesOptions$ observable in the TS file like

this.imageChangeSubscription = this.createVmForm.get('image').valueChanges.subscribe((value) => {
  this.selectedImageVolumeSize = this.imagesOptions$ // get array value and match id and get the size.

If it was an array it would be like

this.selectedImageVolumeSize = this.images.find((image) => image.imageId === value).size;

I want to do it without subscribing to imageOptions$ observable in the imageChangeSubscription to avoid subscribtion in side subscription and without using an extra property in the TS file

Any way to do it?

Upvotes: 1

Views: 239

Answers (2)

Some random IT boy
Some random IT boy

Reputation: 8467

I like the answer that @Joshua McCarthy provided but you don't need to subscribe at all and you can get a "cleaner" (depending from the point of view of the consumer) version.

Kudos for the statement about the shareReplay(1) to avoid extra side-effects (which they are usually API calls, resulting in undesired network consumption). I highly suggest you apply it to your image options observable.

then I would proceed to create another observable:

import { combineLatest, shareReplay } from 'rxjs/operators'

@Component({...})
export class YourComponent {
    ...
    ...
    selectedImageVolumeSize$ = combineLatest([this.createVmForm['image'].valueChanges, this.imagesOptions$]).pipe(
        map(([selectedImageId, imageOptions] => imageOptions.find(item => item.id === selectedImageId)

    )

    constructor(...){}

}

Upvotes: 0

Joshua McCarthy
Joshua McCarthy

Reputation: 1852

You can use switchMap() to avoid nested subscriptions. But you should update imagesOptions$ so it can share its latest value with multiple subscribers using the shareReplay() operator.

imageOptions$: Observable<BoundImagesToProject[]> = this.imagesService
  .getBoundImages({ projectId: this.projectId })
  .pipe(
    map(({data}) => data),
    shareReplay(1)
  );

Then in your subscription, grab the latest value from imageOptions$ to find your size.

this.imageChangeSubscription = this.createVmForm
  .get('image').valueChanges
  .pipe(
    switchMap(value => this.imagesOptions$
      .pipe(
        map(images => images.find(({imageId}) => imageId===value ))
      )
    )
  ).subscribe();

Upvotes: 1

Related Questions