简体   繁体   中英

Dynamically created object returns undefined on attempt to access its key value

I have a student object that looks like this...

{
  Freshmen: [{id: 3}, {id: 5}],
  Sophomores: [{id: 2}, {id: 6}],
  Juniors: [{id: 1}, {id: 8}],
  Seniors: [{id: 9}, {id: 4}, {id: 7}]
}

I need to do a look up for students based on the ids in the arrays. What I need to be returned is an object that looks exactly the same but where instead of the objects containing just the ids, I now have the full object retrieved from the database.

Inside my angular component.ts file, I am attempting to do so in the following way...

  private getStudents(obj) {
    const result = {};
    for (const key of Object.keys(obj)) {
      obj[key].map(item => {
        this.studentsService.geStudentById(item.id).subscribe(res => {
          result[key] = [];
          result[key].push(res);
        });
      });
    }
    return result;
  }

I have a service injected into the component. The service has a method which gets students by the id and I'm calling the service method inside getStudents() . The service returns the respective student objects which I am then pushing into the array.

Later I am calling the function and assigning the results to a variable studentDetails like so...

this.studentDetails = this.getStudents(studentObj);

Everything seems to be working fine except that when I try to do... console.log(this.StudentDetails.Freshmen); for example, I get undefined .

Am I going about this the right way or is there some better way I could handle it, like maybe using arr.reduce() instead of arr.map() ? Although I have tried reduce and it only returns one item for some reason. Your help would be greatly appreciated.

You are calling a asynchronously before return result . What is happening is that your function is returning an empty object {} before the all requests are completed.

You need something like this (need some refactor):

function getStudents(obj) {
const result = {};
const requests=  [];
for (const key of Object.keys(obj)) {
   obj[key].forEach(item => {
    let currentRequest = this.studentsService.geStudentById(item.id)
    .subscribe(res => {
      result[key] = [];
      result[key].push(res);
    });
    requests.push(currentRequest)
  });
}
return Observable.forkJoin(...requests).subscribe(() => {
    return result;
})

}

Then set the studentDetails prop like this:

this.getStudents(studentObj).subscribe(result => this.studentDetails = result)

The problem is that your return statement is executed before your async getStudentById() requests have completed.

I would suggest using RxJS and then zipping your requests together and do something with the data once you have all the results from the requests.

With RxJS your function could look like this:

private getStudents(obj) {
  const result = {};

  // Array to store all the observables
  let observables: Observables<void>[] = [];

  // Generate all the observables with the respective parameters
  for (const key of Object.keys(obj)) {
    obj[key].map(item => {
      observables.push(
        this.studentsService.geStudentById(item.id)
          .map(res => {
            result[key] = [];
            result[key].push(res);
        });
      });
    );
  }

  // Zip them together and wait for all of them to emit a value
  of(...observables)
    .pipe(zipAll())
    .subscribe(() => {
      return result;
    });
}

Additionally, it might be better performance wise to fetch all the students at once and then filtering them instead of making multiple requests just for a single student (depends on the number of students thought).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM