简体   繁体   中英

How to display one Observable of an Observable-Array in Angular?

I have an Observable-Array which contains a list of questions. I would like to display my questions one by one but can't figure out a way to do that.

So far I only managed to display all of them with a *ngFor in my html.

This is my component:

 import { Component, OnInit } from '@angular/core'; import { mergeMap, Observable, of, concatAll, Subject, startWith, zip, Subscription } from "rxjs"; import { Question } from "../models/Question"; import { QuestionService } from "../services/question.service"; import { AuthService } from "../services/auth.service"; import { User } from "../models/User"; @Component({ selector: 'app-play', templateUrl: './play.component.html', styleUrls: ['./play.component.css'] }) export class PlayComponent implements OnInit { user_id: Pick<User, "id"> | undefined unanswered_questions$: Observable<Question[]> | undefined question$: Observable<Question> | undefined constructor( private questionService: QuestionService, private authService: AuthService ) { } ngOnInit(): void { this.user_id = this.authService.userId this.unanswered_questions$ = this.getUnansweredQuestions(this.user_id) } getUnansweredQuestions(user_id: Pick<User, "id"> | undefined): Observable<Question[]> { return this.questionService.fetchAllUnansweredQuestions(user_id); } }

This is my html:

 <mat-card class="question-card" *ngFor="let question of unanswered_questions$ | async"> <mat-card-header> <div mat-card-avatar class="example-header-image"></div> <mat-card-title>{{question.title}}</mat-card-title> </mat-card-header> <mat-card-content> <h3>{{question.body}}</h3> </mat-card-content> <mat-card-actions> <button mat-button>{{question.answer1}}</button> <button mat-button>{{question.answer2}}</button> <button mat-raised-button color="accent">Skip<mat-icon>skip_next</mat-icon></button> </mat-card-actions> </mat-card>

I found this Post where someone is trying to do basically the same thing. Unfortunately both answers on that post don't work for me. I figured that they don't work due to the post being 4 years old and me using a newer version of rxjs and angular.

Any help is much appreciated. Thank you!

Currently you code "works" because you are piping your observable to the async pipe handler which holds executing the ngFor until the observable is resolved.

I would alter your code so that you subscribe to the observable and handle the ensuing result. Actually, I would first convert to a promise and await it since, IMO, that style is more readable and predictable, especially for a "one and done" event.

So alter your component like so (NOTE: I've left off the imports and decorator for sake of brevity):

export class PlayComponent implements OnInit {

  user_id: Pick<User, "id"> | undefined;
  unanswered_questions: Question[];
  question$: Observable<Question> | undefined;
  questionIndex = 0;

  constructor(
    private questionService: QuestionService,
    private authService: AuthService
  ) { }

  async ngOnInit(): Promise<void> {
    this.user_id = this.authService.userId;
    await this.unanswered_questions = this.getUnansweredQuestions(this.user_id).toPromise();
  }

  getUnansweredQuestions(user_id: Pick<User, "id"> | undefined):    Observable<Question[]> {
    return this.questionService.fetchAllUnansweredQuestions(user_id);
  }

  skipQuestion(): void {
    if (this.questionIndex !== this.getUnansweredQuestions.length) {
     this.questionIndex++;
     }
  }
}

And then your HTML:

<mat-card class="question-card" *ngIf="unanswered_questions">
  <mat-card-header>
    <div mat-card-avatar class="example-header-image"></div>
    <mat-card-title>{{unanswered_questions[questionIndex].title}}</mat-card-title>
  </mat-card-header>
  <mat-card-content>
    <h3>{{unanswered_questions[questionIndex].body}}</h3>
  </mat-card-content>
  <mat-card-actions>
    <button mat-button>{{unanswered_questions[questionIndex].answer1}}</button>
    <button mat-button>{{unanswered_questions[questionIndex].answer2}}</button>
    <button mat-raised-button color="accent" (click)="skipQuestion()">Skip<mat-icon>skip_next</mat-icon></button>
  </mat-card-actions>
</mat-card>

By adding ngIf to your matCard you prevent rendering until the observable is resolved.

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