1

In this code below , I want student list to be rendered immediately and not wait for the second observable , but when second observable comes , it should check that student is not enrolled in all courses and then enable the button to add to course.

https://stackblitz.com/edit/angular-ivy-trnr9r?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fapp.component.html,src%2Fapp%2FdummyApi.ts,src%2Fapp%2Fapp.module.ts

enter image description here

enter image description here

enter image description here

Seth
  • 57
  • 10

2 Answers2

1

You need to first create a behaviour subject, the special property of behaviour subject is we can have a initialization value. Then when each api call completes we can update the subject with the latest value.

Learn more about behaviour subject

html

<div class="container">
  <div class="list" *ngIf="studentDataSubject | async as studentData">
    <div *ngFor="let student of studentData">
      <div>{{ student.name }}</div>
      <button class="btn" [disabled]="student.enable">Add to course</button>
    </div>
  </div>

  <div class="list" *ngIf="courseDataList | async as courseData">
    <div *ngFor="let course of courseData">
      <div>{{ course.name }}</div>
    </div>
  </div>
</div>

ts

import { Component, OnInit, VERSION } from '@angular/core';
import { BehaviorSubject, map, Observable, switchMap, tap } from 'rxjs';
import {
  IStudentData,
  getStudentList,
  ICourseData,
  getCourseList,
} from './dummyApi';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  studentDataSubject: BehaviorSubject<any> = new BehaviorSubject<any>([]);
  name = 'Angular ' + VERSION.major;

  studentDataList: Observable<IStudentData[]> = getStudentList(); // no delay
  courseDataList: Observable<ICourseData[]> = getCourseList(); // data comes after 3 seconds

  ngOnInit() {
    this.studentDataList
      .pipe(
        switchMap((studentData) => {
          this.studentDataSubject.next(studentData);
          return this.courseDataList.pipe(
            tap((courseData) => {
              const newStudentData = studentData.map((student) => {
                let dummyStudentObj = {
                  ...student,
                  enable: student.courseCount < courseData.length,
                };
                return dummyStudentObj;
              });
              this.studentDataSubject.next(newStudentData);
            })
          );
        })
      )
      .subscribe();
  }
}

forked stackblitz

Naren Murali
  • 19,250
  • 3
  • 27
  • 54
  • Thanks for reply , but it has to be done reactively. Manual subscription is not allowed , only async pipe is accepted. So can you do it without subscribing manually in ngOnInit – Seth Aug 23 '22 at 15:37
1

studentDataList.pipe(switchMap(...courseDataList...))

to

courseDataList.pipe(switchMap(...studentDataList...))

ts

export class AppComponent {
  name = 'Angular ' + VERSION.major;

  studentDataList: Observable<IStudentData[]> = getStudentList(); // no delay
  courseDataList: Observable<ICourseData[]> = getCourseList(); // data comes after 3 seconds

  resultData = this.courseDataList.pipe(
    map(({length}) => length),
    // startWith(Infinity),
    switchMap((len) => this.studentDataByLength(len))
  );

  studentDataByLength(length: number){
    return this.studentDataList.pipe(
      map((datas) => datas.map((d) => ({...d, enable: d.courseCount < length})))
    );
  }

  ngOnInit() { }
}

html

<div class="container">
  <div class="list" *ngIf="resultData | async as studentData">
    <div *ngFor="let student of studentData">
      <div>{{ student.name }}</div>
      <button class="btn" [disabled]="student.enable">Add to course</button>
    </div>
  </div>

  <div class="list" *ngIf="courseDataList | async as courseData">
    <div *ngFor="let course of courseData">
      <div>{{ course.name }}</div>
    </div>
  </div>
</div>
Eddy Lin
  • 513
  • 2
  • 6
  • Hi @Eddy , thanks for reply. But this doesn't solve the problem , using this also I have to wait until data of course observable comes , only then the student data shows. I want student data to show earlier , but when course data comes then it changes the button states – Seth Aug 24 '22 at 06:57
  • uncomment this line // startWith(Infinity) @Seth – Eddy Lin Aug 24 '22 at 07:08
  • it will still only show after the courseList comes which comes after 3 seconds. I tried uncommenting , still both comes together . I need studentList to comes first as there is no delay in that API and then after 3 seconds next on comes and changes button states – Seth Aug 24 '22 at 07:14
  • 1
    https://stackblitz.com/edit/angular-ivy-tn4ryt like this? – Eddy Lin Aug 24 '22 at 07:14
  • Yes , it works . Can you please explain how it worked. Thanks – Seth Aug 24 '22 at 07:30
  • 1
    courseDataList need delay 3 sec, so just use `startWith(Infinity)` make it trigger `switchMap` on first time – Eddy Lin Aug 24 '22 at 09:15