I have a data structure that consists of two classes: Repo
and Contributor
.
export class Repo {
id: number;
name: string;
contributors: Contributor[];
}
export class Contributor {
id: number;
login: string;
}
Currently, I am using Observable<Repo>
to retrieve all repository data. However, I want to ensure that before the outer Repo
is emitted fully, an inner observable Observable<Contributor>
is called to populate all contributors associated with the repo. I am facing challenges in achieving this and not sure how to proceed. Below is the code snippet I have been working on:
private repos: Repo[] = [];
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
// This part does not wait for all contributors to be resolved
map(repo => this.getRepoContributors(orgName, repo))
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
// Function to fetch contributors data for the repo
private getRepoContributors(orgName: string, repo: Repo): Repo {
const contributors: Contributor[] = [];
repo.contributors = contributors;
this.githubService.getRepoContributors(orgName, repo.name)
.subscribe(
contributor => {
// Add the contributors to the collection for this repo
contributors.push(contributor);
},
error => console.log(error),
() => repo.contributors = contributors
);
return repo;
}
As my knowledge on Observable
is limited, I have been struggling with this issue for some time now. I have tried searching for solutions on StackOverflow without success. Any assistance or guidance would be highly appreciated!
(The code is implemented in Angular 5)
Solution:
After considering @joh04667's suggestion below, I was able to make it work by implementing the following changes:
getRepos(orgName: string): void {
const repoBuild: Repo[] = [];
this.githubService.getRepos(orgName).pipe(
// Using mergeMap() to replace `repo` with the result from `getRepoContributors`
mergeMap(repo => this.getRepoContributors(orgName, repo))
).subscribe(
repo => repoBuild.push(repo),
error => {
this.repos = [];
console.log(error);
},
() => this.repos = repoBuild
);
}
// Function to fetch contributors data for the repo
private getRepoContributors(orgName: string, repo: Repo): Observable<Repo> {
repo.contributors = []; // Ensure each repo has an empty array of contributors
return this.githubService.getRepoContributors(orgName, repo.name).pipe(
// Utilizing tap() to inspect each contributor and add them to the array
tap(contributor => {
// Add the contributors to the total collection for this repo
repo.contributors.push(contributor);
}),
// Selecting only the last contributor and replacing them with the repo
last(
() => false,
() => repo,
repo
)
);
}
In the final step where last()
is used, I inform the Observable
that although it will process all values, only the last one will be consumed. Despite being of type Contributor
, I replace it with a default value (the repo), which helps transform the return type from Observable<Contributor>
to Observable<Repo>
as required by the overarching Observable.