Return all fields from a document as an array from Firebase cloud Firestore - ionic 4

I am trying to return all fields from a document from Firebase cloud Firestore. The problem arises when I wish to store this document as an array on my page for use. In whatever instance I try I cannot seem to be able to take the array outside of the .subscribe() method. outside of the subscribe the array is undefined. I assume I am not understanding a process in typescript.

quiz.service.ts

 export interface Quiz{
  q1: string;
  q2: string;
  q3: string;
  q4: string;
  }
  @Injectable({
    providedIn: 'root'
  })
  export class QuizService {
    quizss: Quiz[];
    quiz: Quiz;
    private quizCollection: AngularFirestoreCollection<Quiz>;
    private quizs: Observable<Quiz[]>;

    constructor(db: AngularFirestore) {
      this.quizCollection = db.collection<Quiz>('quiz');

      this.quizs = this.quizCollection.snapshotChanges().pipe(
        map(actions =>{
            return actions.map(a => {
              const data = a.payload.doc.data();
              const id = a.payload.doc.id;
              return{ id, ...data};
            });
        })
      );
     }

    getQuiz(id){
      return this.quizCollection.doc<Quiz>(id).valueChanges().pipe(
        map(response => response)
      );
    }
  }

I get undefined with the following at the console.log in ngOnInit:

ngOnInit() {
     this.route.queryParams.subscribe((res) => {
       id = res.name;
     });
     answerNumber= 0;
     this.question2 = this.quizService.getQuiz(id);
     console.log("question pull "+ this.question2[0]);  
 }
 returnQuiz( answerNumber) {
     this.quizService.getQuiz(id)
       .subscribe(res =>{
       this.quiz = res;
       let newPoint = {
         q1: this.quiz.q1,
         q2: this.quiz.q2,
         q3: this.quiz.q3,
         q4: this.quiz.q4,
       }
       this.question1[0] = newPoint.q1;
       this.question1[1] = newPoint.q2;
       this.question1[2] = newPoint.q3;
       this.question1[3] = newPoint.q4;

       return this.question1;
     });
  }

I have also tried this with the same error on the console log returning undefined:

ngOnInit() {
     this.route.queryParams.subscribe((res) => {
       id = res.name;
     });
     answerNumber= 0;
     this.quizService.getQuiz(id)
       .subscribe(res =>{
       this.quiz = res;
       let newPoint = {
         q1: this.quiz.q1,
         q2: this.quiz.q2,
         q3: this.quiz.q3,
         q4: this.quiz.q4,
       }
       this.question1[0] = newPoint.q1;
       this.question1[1] = newPoint.q2;
       this.question1[2] = newPoint.q3;
       this.question1[3] = newPoint.q4;
     });
      console.log("question pull "+ this.question1[0]);
 }

It’s not TypeScript, it’s a mismatch between imperative programming (what you’re trying to do, and likely the mindset most all of us started with) and reactive/functional programming (what you need to do to survive writing webapps).

Imagine you’re the head of a large spy organization. You can’t micromanage the details of each of your agents’ missions, because you aren’t there and can’t know in advance when or if they’ll run into X or Y challenge. The best you can do is train them: “hey, if X happens to you, then you need to immediately go to the nearest safehouse”; “if you figure out the name of the enemy agent behind Y, then fire off a carrier pigeon and tell agent 99 - they’ll handle it from there”.

This is roughly the situation we’re in with reactive programming. The subscribe block is effectively your agent field manual. You have to write it in advance, but have no way of knowing when (or even if in some cases) it’ll be used. Anyplace outside the field, it’s just a book. It can’t take action on its own.

The first most crucial step you absolutely have to take here is to meticulously give every object property, function parameter, and return value a proper type (no any). You’ve already got a Quiz interface, which is excellent. However, returnQuiz doesn’t declare a return value type, and if it did, a lot of this would suddenly become crystal-clear.

The second important step is to rigorously separate methods into two types: those who produce a result (“functions”) and those who do something (“procedures”). Do your absolute best to avoid hybrids - they are responsible for many subtle and frustrating bugs.

Functions cannot modify external state

There is lots of assignment to this. things in returnQuiz. All of them need to go away if it’s a function. So does the subscribe call, because subscribing is an action, and functions don’t take actions. If you decide to go this route, you must decide what you want returnQuiz to return. Unfortunately, your first choice is likely to be Quiz, and that’s simply not possible. Ever. Really. The best you can do is Observable<Quiz>, pushing the burden of subscription to callers of returnQuiz.

Procedures must always return void

If you want returnQuiz to keep its subscribe and its assignment to controller properties, it can’t attempt to return anything (and needs a new name).

Either of those options is doable, so take some time to think about which way you want to go.

2 Likes