0

I am fetching list of categories with the name and ID from the first API and then inside this list of categories I integrate the list of lectures, which display the lectures based on the ID from the category (from the second API).

Everything works fine, the only issue is that I am getting the lectures in WRONG order, like the wrong ID is passed in my function.

My typescript code:

 ngOnInit(): void {
   this.getCategories();
   }


getCategories() {
    this.isLoaded = false;

    this.categoryListSubs = this.categoryService
      .getAllCategories()
      .subscribe((categories: any) => {
        this.allDataFromApi = categories;
        this.categoryList = categories.results;

        this.isLoaded = true;

        this.categoryList.forEach((el: any) => {
          this.categoryID = el.id;
          this.getLectures(this.categoryID);
        });
      });
  }

  getLectures(id: any): void {
    this.isLoaded = false;

    this.lecturesArray = [];
    this.allLecturesOfCategorySubs = this.categoryService
      .getLecturesOfCategoryViewAll(id)
      .subscribe((allLectures: AllLecture) => {
        this.lecturesArray.push(allLectures.results);
        this.isLoaded = true;
      });
  }

My HTML code:

    <div
      class="categories"
      *ngFor="let category of categoryList; let i = index"
    >
        <div class="d-flex mt-4 category-button py-1">
          <h2>
            {{ category.name }} <span>({{ category.lectures }})</span>
          </h2>
        </div>
        <div class="videos">     
            <div *ngFor="let lecture of lecturesArray[i]">
              <app-video-item
                [latestLecturesList]="lecture"
                [listenToListView]="false"
              ></app-video-item>
            </div>
        </div>
    </div>

So if i have then displayed 3 categories like computer science, biology, network the lectures which are in category biology are displayed under computer science and lectures which are in network category are displayed under biology and so on..

11
  • If the data isn't sorted from your back-end then you need to sort it. Commented Jun 1, 2022 at 11:02
  • You should sort lectureArray with respect to categories. After being sure order of the arrays respectively, you can use child loop Commented Jun 1, 2022 at 11:02
  • 1
    I think this is not a good practice. You cannot guarantee in which order the secondary calls are completed, so the order of the lectures by categories can change. It's better to get all the categories and lectures in just 1 call if you are able to refactor the API. Commented Jun 1, 2022 at 11:03
  • Can someone give me example how you think, that I should sort? Because I am passing the right IDs but sadly in wrong order Commented Jun 1, 2022 at 11:04
  • You can see when debugging, order of the arrays different than your think. @HarunYilmaz 's answer is more acceptable. You can get parent and child elements together from same api. Commented Jun 1, 2022 at 11:07

1 Answer 1

1

I think your problem is that you get multiple categories and call getLecturesOfCategoryViewAll for each category. When you receive a result you just push it into an array. But there is no guarantuee which call finishes first, so it can easily happen that the second call completes before the first, hence the order is incorrect.

What you can to do is to combine the selection of categories and their lecturers into one stream.

I created a stack blitz to play around with this: https://stackblitz.com/edit/typescript-lqhbtz?file=index.ts

// 01. Fetch categories
const categoriesAndLecturers$ = getCategories().pipe(
  // 02. Map response to Category[]
  map((response) => response.items),
  // 03. Convert each category into a separate rxjs emit
  concatMap((categories) => fromArray(categories)),
  // 04. Fetch lecturers for each category
  mergeMap((category) =>
    // 05. fetch the lecturers for current category
    getLecturerByCategory(category.id).pipe(
      // 06. Map response to Lecturer[]
      map((response) => response.items),
      // 07. And map this to an object including category and lecturer
      map((lecturers) => ({ category, lecturers }))
    )
  ),
  // 08. Convert the single rxjs events back to an array
  toArray()
);

This will give you an observable which returns you a list of objects. Those objects have a field category containing the category data and a field lecturers that belong to the category.

I'll explain what the stream does, but if you want to play around with it, you can start removing operators from the bottom (or add a tap operator with a console log statement between each operator) to better understand what is happening.

  • 01: This is just the call to your api.
  • 02: Here we convert the response from the api into Category[]
  • 03: We receive an array of categories (Category[]), but we want to operate on single categories not on the whole array. For this we can use concatMap and the fromArray factory. This will change the type of stream from Category[] to Category
  • 04: Now we use the mergeMap operator to call getLecturersByCategory (which will be called for each emitted Category).
  • 05/06: In this inner pipe we first map the response to a lecturer array (Lecturer[]).
  • 07: The second map we create a new object out of the existing Category category and Lecturer[] lecturers.
  • 08: The toArray operator waits until the stream completes and merges all sent values (in this case combination of category and their lecturers) into an array.

So in your component you could do something like this:

categoriesAndLecturers$.subscribe(data => this.categoriesWithLecturers = data);

And then in your template you can use it like this:

    <div
      class="categories"
      *ngFor="let item of categoriesWithLecturers; let i = index"
    >
        <div class="d-flex mt-4 category-button py-1">
          <h2>
            {{ item.category.name }} <span>({{ item.category.lectures }})</span>
          </h2>
        </div>
        <div class="videos">     
            <div *ngFor="let lecture of item.lecturers">
              <app-video-item
                [latestLecturesList]="lecture"
                [listenToListView]="false"
              ></app-video-item>
            </div>
        </div>
    </div>
Sign up to request clarification or add additional context in comments.

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.