0

I have an array of device_ids. The array is extended dynamically by selecting items on a UI. Initially and whenever the array is updated, I need to iterate through it and fetch data for each device_id. G

Getting data is basically a database request (to Firestore) which returns an Observable. By using switchMap I fetch some data from other database requests.

In the end I'd like to have something like an array of objects / Observables with all data I can subscribe to. My goal is using Angular's async pipe in my HTML then.

Is that possible (e.g. with RxJS)? I'm not sure how to solve this...

This is how my code currently looks like:

// Init
devices$: Observable<any;

// Array of device_ids
device_ids = ['a', 'b'];

// Get device data. This will initially be called on page load.
getDeviceItemData(device_ids) {

    // Map / loop device_ids
    device_items.map(device_id => {

        // getDeviceById() returns an Observable
        return this.getDeviceById(device_id).pipe(

        // Switch to request additional information like place and test_standard data
        switchMap(device => {

            const place$ = this.getPlaceById(device['place_id]');
            const test_standard$ = this.getTestStandardById(device['test_standard_id]');

            // Request all data at the same time and combine results via pipe()
            return zip(place$, test_standard$)
                .pipe(map(([place, test_standard]) => ({ device, place, test_standard })));
        })
        ).subscribe(device_data => {
            // Do I need to subscribe here?
            // How to push device_data to my Observable devices$?
            // Is using an Observable the right thing?
        });
    });
}

// Add device
addDevice(device_id) {

    // Add device_id to array
    this.device_ids.push(device_id);

    // Fetch data for changed device_ids array
    getDeviceItemData(this.device_ids);
}

Preferred HTML / Angular code by using async pipe:

<div *ngFor="device of devices$ | async">{{ device.name }}</div>

Thank you for any help!

2 Answers 2

2

This is definitely possible with rxjs. You are on the right path. I've made a small modification to your getDeviceItemData() method.

Once the device info is retrieved by id, then you can use forkJoin to execute the two calls to get place and test_standard data parallelly and then map over that data to return the object that you need which has info of device, place and test_standard as an observable. And since we're mapping over device_ids, it'll produce an array of observables containing the required objects so that you can easily club them with async pipe.

Please note: You do not have to subscribe to the devices$ because the async pipe will automatically do that for you.

Please see the below code:

  // Init
  devices$: Observable<{ device: any, place: any, test_standard: any }>[];

  // Array of device_ids
  device_ids = ['a', 'b'];

  getDeviceId: (x) => Observable<any>;
  getPlaceById: (x) => Observable<any>;
  getTestStandardById: (x) => Observable<any>;

  getDeviceItemData() {
    this.devices$ = this.device_ids.map((id) =>
      this.getDeviceId(id).pipe(
        switchMap((device) => {
          return forkJoin([
            this.getPlaceById(device['place_id']),
            this.getTestStandardById(device['test_standard_id'])
          ]).pipe(map((y) => ({ device, place: y[0], test_standard: y[1] })));
        })
      )
    );
  }

In your HTML, you'll have to do:

EDIT: Since devices$ is an array of observables, we need to iterate over individual observables and apply async pipe on every observable.

<div *ngFor="device of devices$">
  <div *ngIf="device | async as device"> 
    <div>{{ device.device.name }}</div>
    <div>{{ device.place}}</div>
    <div>{{ device.test_standard }}</div>
  </div>
</div>
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you, I applied your approach but I guess I'm missing something. My browser console gives me the following error "ERROR Error: InvalidPipeArgument: '[object Object],[object Object]' for pipe 'AsyncPipe'". Furthermore, if I try to debug it and subscribe to devices$ in my .ts and console.log the result, it displays the data in two lines and not in an array. Do you have an idea why that happens?
This shows my console if I console.log(this.devices$): "(2) [Observable, Observable]". I can't use the async pipe in my HTML, please see my previous comment. I guess something is missing? Thank you in advance.
You're right. This will return an array of observables. And async pipe works with just a single observable or an observable of an array (with ngFor). What we have here is an array of observables. In this case, we will have to iterate through the devices$ first and for each device, we need to have an async pipe. Updating the answer. Please check if it works.
0

With forkJoin you can wait for all observables to complete. Alternatively you could use combineLatest which will give you the latest data combination, every time one observable emits data. This will cause more events, but will not wait for completion of all.

getDeviceItemData(device_ids) {
  const arrayOfObservables = device_ids.map(device_id => {
    return this.getDeviceById(device_id).pipe(...);
  }
  return combineLatest(arrayOfObservables); // Observable<Data[]>
}

 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.