Reputation: 1996
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.
Upvotes: 0
Views: 271
Reputation: 9124
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
Upvotes: 1
Reputation: 31115
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.
Upvotes: 1