Angular Nested *ngFor is giving Infinite results on being used in a HTML Template

Viewed 45

I'm using Github API to built a Repositories Viewer. I'm taking repo.name as an input for another variable and passing it as a parameter to a function which indeed calls to get the Languages used as in a repository.

The problem is that it not displaying individual set of languages for a repository, it's looping them all and running infinitely.

HTML File

 <div class="user-repos">
    <ul id="repos" *ngFor="let repo of repos | paginate:{ id:'listing_repos',itemsPerPage:10, currentPage:page, totalItems:totalRecords}">
      <li>
        <h4>{{ repo.name | uppercase}}</h4>
        <p>{{ repo.description }}</p>
          <ul *ngFor="let lang of languagesUsed(repo.name)">
              <li>{{lang}}</li>
          </ul>
      </li>
    </ul>
  </div>

Component.ts file for the same

export class UserComponentComponent implements OnInit {

  @Input() username:any;
  
  constructor(private githubUser:UserDataService) { }
  repos:Array<any> = [];
  avatar:any;
  name:String = '';
  location:String = '';
  bio:String = '';
  twitter:String = ''; 
  github:String = '';
  totalRecords = 0;
  page = 1;
  langs:Array<any> = [];
  

  getUser(){
    this.githubUser.getData(this.username).subscribe((data)=>{
        //console.log(data);

        this.avatar = data.avatar_url;
        this.name = data.name;
        this.location = data.location;
        this.bio = data.bio;
        this.twitter = `https://twitter.com/${data.twitter_username}`;
        this.github = `https://github.com/${data.login}`;
    })
  }

  getRepos(){
    this.githubUser.getRepositories(this.username).subscribe((repos)=>{
        this.repos = repos;
        this.totalRecords = repos?.length;
        // this.languagesUsed = Object.keys(repos.languages_url);
    })
  }

  languagesUsed(repoName:any){
      this.githubUser.getLanguages(this.username, repoName).subscribe((languages)=>{
        // console.log(Object.keys(languages));
        this.langs = Array.from(Object.keys(languages));
      })
      return this.langs;
    }

    ngOnInit(): any{
      this.getUser();
      this.getRepos();
    }

  }

Service.ts for fetching the API's

export class UserDataService {

  constructor(private http:HttpClient) {}

  getData(username:String):Observable<any>{

    let user = `https://api.github.com/users/${username}`;
    return this.http.get(user);
  }

  getRepositories(username:String):Observable<any>{
    let repos = `https://api.github.com/users/${username}/repos`;
    return this.http.get(repos);
  }

  getLanguages(username:String, repo:String):Observable<any>{
    let languages = `https://api.github.com/repos/${username}/${repo}/languages`;
    return this.http.get(languages);
  }
}
1 Answers

This issue is caused because of languagesUsed function, couple of problems with it

  1. Used as a binding languagesUsed()
  2. Due to the async call, returned value this.langs won't be the right value.
  3. On each iteration, it will make an ajax call. This is causing an infinite loop

To fix this, you move the fetch languages logic as a serial operation (switchMap) on the observable level. As soon as you received a specific repo, make another call to bring the languages for that user. Thereafter extend the repo object with the languages array received.

Something like below

// after receiving the repos, 
const allRepoWithLanguages = repos.map(repo => 
  // loop over them and make a `getLanguages` ajax call
  this.githubUser.getLanguages(this.username, repo.name)
  .pipe(
     // extend repo with languages
     map(languages => ({...repo, languages }))
  )
);

Let's extend the logic just we've seen into actual implementation.

TS

getRepos(){
    this.githubUser.getRepositories(this.username).pipe(
      switchMap(repos => {
         const allRepoWithLanguages = repos.map(repo => 
            this.githubUser.getLanguages(this.username, repo.name)
            .pipe(
               // amending repo with languages
               map(languages => ({...repo, languages }))
            )
         );
         return forkJoin(allRepoWithLanguages);
      }),
    ).subscribe((repos)=>{
        this.repos = repos;
        this.totalRecords = repos?.length;
    })
}

HTML

<div class="user-repos">
   <ul id="repos" *ngFor="let repo of repos | paginate:{ id:'listing_repos',itemsPerPage:10, currentPage:page, totalItems:totalRecords}">
      <li>
        <h4>{{ repo.name | uppercase}}</h4>
        <p>{{ repo.description }}</p>
          <ul *ngFor="let lang of repo.languages">
              <li>{{lang}}</li>
          </ul>
      </li>
   </ul>
</div>
Related