简体   繁体   中英

optimise api calls rxjs

I have an array , and i want to loop through the array and call server data on each item . I want to show a spinner as api is being in pending state as i load the page.

Current solution:

  getRecordings(classroom) {
    return this.sysAdminService.getMeetingRecordings(classroom.meeting_id).subscribe(res => {
      classroom.recordings = res;
    });
  }

Api calls:

 this.classRoomsToDisplay.map((classroom) => {
      classroom.showAttendees = false;
      classroom.recordings = null;
      this.getRecordings(classroom);
    });

As above im subscribing to the observable for each item in an array .I want to find if there is any better way to do this using rxjs , concatMap? i dont want to wait for the api calls for the entire array to be completed , i want to display the result as soon as the api call for that item is completed.

From the data you've shown, it isn't clear which property you're using to display the data. I'll assume it's classroom.showAttendees since it's a boolean.

You could use RxJS forkJoin function to combine multiple observables and subscribe once instead of having multiple subscriptions. And you could toggle the showAttendees flag for each item in the array as a side-effect using tap operator.

Try the following

complete$ = new Subject<any>();

getRecordings(classroom): Observable<any> {   // <-- return the HTTP request observable
  return this.sysAdminService.getMeetingRecordings(classroom.meeting_id).pipe(
    tap => {
      classroom.showAttendees = true;         // <-- set the flag to show data
      classroom.recordings = res;
    }
  );
}

ngOnInit() {
  // `reqs` is an array of observables `[getRecordings(classroom), getRecordings(classroom), ...]`
  const reqs = this.classRoomsToDisplay.map((classroom) => {
    classroom.showAttendees = false;
    classroom.recordings = null;
    return this.getRecordings(classroom);
  });

  forkJoin(reqs).pipe(takeUntil(this.complete$)).subscribe();   // <-- trigger the HTTP requests
}

ngOnDestroy() {
  this.complete$.next();        // <-- close open subscriptions
}

Template

<div *ngFor="classroom of classRoomsToDisplay">
  <ng-container *ngIf="classroom.showAttendees">
    {{ classroom.recordings }}
  </ng-container>
</div>

Now since we're assigning the values in the tap operator, data for each item will be shown as soon as they arrive.

You should prefer using the async pipe instead of the subscription.

Example:

<ul>
  <li *ngFor="let attendee of attendeesWithData">Attendee: {{ attendee.attendee }}, duplicated: {{ (attendee.duplication | async) || 'pending' }}</li>
</ul>

In this example the async operation is a duplication of the string and the request takes longer each time for demonstration.

@Injectable()
export class DuplicateService {

  times = 1;

  constructor() { }

  getDuplicated(s: string): Observable<string> {
    return timer(1000 * this.times++).pipe(
      map(() => s + s),
    );
  }
}
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  private attendees: string[] = ['A', 'B', 'C', 'D', 'E', 'F'];

  attendeesWithData = this.attendees.map(attendee => ({ 
    attendee,
    duplication: this.duplicateService.getDuplicated(attendee),
  }));

  constructor(private duplicateService: DuplicateService) {}
}

As a result each second one list item switches its state from pending to the duplicated string.

See also the stackblitz: https://stackblitz.com/edit/angular-ivy-rtx6cg?file=src/app/app.component.ts

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM