简体   繁体   中英

Loading images together with short list of users in Angular ngOnInit method fails, images are always undefined

I have a page containing few bootstrap cards (roughly 2 to 5). They present student info. I also want to load images of these students (from the server) together with their details but I can't figure out how. Here is what I got:

students: { student: Student, hasImage: boolean, image: any }[] = [];
loadingData: boolean;
imageToShow: any;

constructor(private userService: UsersService, private fileService: FilesService) {
}

createImageFromBlob(image: Blob) {
    const reader = new FileReader();
    reader.addEventListener('load', () => {
        this.imageToShow = reader.result;
    }, false);
    if (image) {
        reader.readAsDataURL(image);
    }
}

ngOnInit() {
    this.userService.getStudentsOfLoggedUser().subscribe(res => {
        this.loadingData = true;
        res.forEach(std => {
            if (std.imageUrl == null) {
                this.students.push({student: std, hasImage: false, image: undefined});
            } else {
                this.fileService.getImage(std.imageUrl).subscribe(data => {
                    this.createImageFromBlob(data);
                });
                this.students.push({student: std, hasImage: true, image: this.imageToShow});
            }
            this.loadingData = false;
        });
    });
}

this code fails to get the images from the server, they are always missing (undefined). How it should look like to make it right?

You must put this.students.push({student: std, hasImage: true, image: this.imageToShow}); inside this.fileService.getImage subscription because its execution is async.

On the other hand you must avoid subscriptions nested. Check this link https://coryrylan.com/blog/angular-multiple-http-requests-with-rxjs

Also if you use subscriptions you must unsubscribe them in ngOnDestroy method to avoid memory leaks. Check this other link https://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/

I hope that it will be useful to you

Regards

From my understanding, the reason why this is happening is because forEach is synchronous in nature, but this particular line is asynchronous:

this.fileService.getImage(std.imageUrl).subscribe(data => {
                    this.createImageFromBlob(data);
});

Instead you can Promises like this:

  this.userService.getStudentsOfLoggedUser().subscribe(res => {
  this.loadingData = true;
  let count = 0;
  new Promise((resolve, reject) => {
    res.forEach(std => {
      count++;
      if (std.imageUrl == null) {
        this.students.push({ student: std, hasImage: false, image: undefined });
      } else {
        this.fileService.getImage(std.imageUrl).subscribe(data => {
          this.createImageFromBlob(data);
        });
        this.students.push({ student: std, hasImage: true, image: this.imageToShow });
      }
      if (count > res.length -1) {
        resolve(); //Resolve condition
      }
    });
  }).then(() => {
    this.loadingData = false;
  })
});

This way, your code will be resolved only when all the async calls are completed, preventing you to get undefined data.

After combining all the suggestions and answers together the working code look like this:

createImageFromBlob(studentCard: StudentCard, image: Blob) {
    const reader = new FileReader();
    reader.addEventListener('load', () => {
        studentCard.image = reader.result
    }, false);
    if (image) {
        reader.readAsDataURL(image);
    }
}

ngOnInit() {
    this.userService.getStudentsOfLoggedUser().subscribe(res => {
        this.loadingData = true;
        res.forEach(std => {
            const studentCard = new StudentCard();
            studentCard.student = std;
            if (!std.imageUrl || std.imageUrl == null) {
                studentCard.hasImage = false;
            } else {
                studentCard.hasImage = true;
            }
            this.students.push(studentCard);
        });
        let count = 0;
        this.students.forEach(std => {
            if (std.hasImage) {
                count++;
                new Promise((resolve, reject) => {
                    this.fileService.getImage(std.student.imageUrl).subscribe(data => {
                        this.createImageFromBlob(std, data);
                        console.log(std);
                    });
                    if (count > this.students.length - 1) {
                        resolve(); //Resolve condition
                    }
                }).then(() => {
                    // there is nothing to do here 
                });
            }
        });
        this.loadingData = false;
    });
}

also except anonymous type I declared StudentCard used to exchange data:

export class StudentCard {
student: Student;
hasImage: boolean;
image: any;

}

and now all images appear on the screen as expected

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