2
\$\begingroup\$

I could use some help refactoring my component and service. I have a lot of nested logic and wasn't sure what I could do to improve this. I'm new to Angular 7 and their piping syntax. I suspect this is what I could use in this example to improve my code? Also, I have an issue where the drawPoll() function is being called before the forEach iteration is done so I manually added a 3 second setTimeout() for the time being. I think that refactoring these subscriptions to not be so nested and also be synchronous might fix this issue.

poll.component.ts

import { Component, OnInit } from '@angular/core';
import * as Chart from 'chart.js';
import { Observable } from 'rxjs';
import { FirebaseService } from '../services/firebase.service';
import { Input, Output, EventEmitter } from '@angular/core';
import { CardModule } from 'primeng/card';

@Component({
  selector: 'app-poll',
  templateUrl: './poll.component.html',
  styleUrls: ['./poll.component.scss']
})
export class PollComponent implements OnInit {
  chart:any;
  poll:any;
  votes:[] = [];
  labels:string[] = [];
  title:string = "";
  isDrawn:boolean = false;
  inputChoices:any = [];

  @Input()
  pollKey: string;

  @Output()
  editEvent = new EventEmitter<string>();

  @Output()
  deleteEvent = new EventEmitter<string>();

  constructor(private firebaseService: FirebaseService) { }

  ngOnInit() {
    this.firebaseService.getPoll(this.pollKey).subscribe(pollDoc => {
      // ToDo: draw poll choices on create without breaking vote listener
      console.log("details?", pollDoc);
      // Return if subscription was triggered due to poll deletion
      if (!pollDoc.payload.exists) {
        return;
      }
      const pollData:any = pollDoc.payload.data();
      this.poll = {
        id: pollDoc.payload.id,
        helperText: pollData.helperText,
        pollType: pollData.pollType,
        scoringType: pollData.scoringType,
        user: pollData.user
      };
      this.title = this.poll.pollType == 1 ? "Title 1" : "Title 2"

      this.firebaseService.getChoices(this.pollKey).subscribe(choices => {
        this.poll.choices = [];
        choices.forEach(choice => {
          const choiceData:any = choice.payload.doc.data();
          const choiceKey:any = choice.payload.doc.id;
          this.firebaseService.getVotes(choiceKey).subscribe((votes: any) => {
            console.log("does this get hit on a vote removal?", votes.length);
            this.poll.choices.push({
              id: choiceKey,
              text: choiceData.text,
              votes: votes.length
            });
          })
        });
        setTimeout(() => {
          this.drawPoll();
        }, 3000);
      });
    });
  }

  drawPoll() {
    if (this.isDrawn) {
      this.chart.data.datasets[0].data = this.poll.choices.map(choice => choice.votes);
      this.chart.data.datasets[0].label = this.poll.choices.map(choice => choice.text);
      this.chart.update()
    }
    if (!this.isDrawn) {
      this.inputChoices = this.poll.choices;
      var canvas =  <HTMLCanvasElement> document.getElementById(this.pollKey);
      var ctx = canvas.getContext("2d");
      this.chart = new Chart(ctx, {
        type: 'horizontalBar',
        data: {
          labels: this.poll.choices.map(choice => choice.text),
          datasets: [{
            label: this.title,
            data: this.poll.choices.map(choice => choice.votes),
            fill: false,
            backgroundColor: [
              "rgba(255, 99, 132, 0.2)",
              "rgba(255, 159, 64, 0.2)",
              "rgba(255, 205, 86, 0.2)",
              "rgba(75, 192, 192, 0.2)",
              "rgba(54, 162, 235, 0.2)",
              "rgba(153, 102, 255, 0.2)",
              "rgba(201, 203, 207, 0.2)"
            ],
            borderColor: [
              "rgb(255, 99, 132)",
              "rgb(255, 159, 64)",
              "rgb(255, 205, 86)",
              "rgb(75, 192, 192)",
              "rgb(54, 162, 235)",
              "rgb(153, 102, 255)",
              "rgb(201, 203, 207)"
            ],
            borderWidth: 1
          }]
        },
        options: {
          events: ["touchend", "click", "mouseout"],
          onClick: function(e) {
            console.log("clicked!", e);
          },
          tooltips: {
            enabled: true
          },
          title: {
            display: true,
            text: this.title,
            fontSize: 14,
            fontColor: '#666'
          },
          legend: {
            display: false
          },
          maintainAspectRatio: true,
          responsive: true,
          scales: {
            xAxes: [{
              ticks: {
                beginAtZero: true,
                precision: 0
              }
            }]
          }
        }
      });
      this.isDrawn = true;
    }
  }

  vote(choiceId) {
    if (choiceId) {
      const choiceInput:any = document.getElementById(choiceId);
      const checked = choiceInput.checked;
      if (checked) this.firebaseService.incrementChoice(choiceId);
      if (!checked) this.firebaseService.decrementChoice(choiceId);
      this.poll.choices.forEach(choice => {
        const choiceEl:any = document.getElementById(choice.id);
        if (choiceId !== choiceEl.id && checked) choiceEl.disabled = true;
        if (!checked) choiceEl.disabled = false;
      });
    }
  }

}

firebase.service.ts

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { map, switchMap, first } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireAuth } from '@angular/fire/auth';

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  // Source: https://github.com/AngularTemplates/angular-firebase-crud/blob/master/src/app/services/firebase.service.ts
  constructor(public db: AngularFirestore, private afAuth: AngularFireAuth) { }

  getPoll(pollKey) {
    return this.db.collection('polls').doc(pollKey).snapshotChanges();
  }

  getChoices(pollKey) {
    return this.db.collection('choices', ref => ref.where('poll', '==', pollKey)).snapshotChanges();
  }


  incrementChoice(choiceKey) {
    const userId = this.afAuth.auth.currentUser.uid;
    const choiceDoc:any = this.db.collection('choices').doc(choiceKey);
    // Check if user voted already
    choiceDoc.ref.get().then(choice => {
      let pollKey = choice.data().poll
      this.db.collection('votes').snapshotChanges().pipe(first()).subscribe((votes:any) => {
        let filteredVote = votes.filter((vote) => {
          const searchedPollKey = vote.payload.doc._document.proto.fields.poll.stringValue;
          const searchedChoiceKey = vote.payload.doc._document.proto.fields.choice.stringValue;
          const searchedUserKey = vote.payload.doc._document.proto.fields.user.stringValue;
          return (searchedPollKey == pollKey && searchedChoiceKey == choiceKey && searchedUserKey == userId);
        });
        if (filteredVote.length) {
          // This person aleady voted
          return false;
        } else {
          let votes = choice.data().votes
          choiceDoc.update({
            votes: ++votes
          });
          const userDoc:any = this.db.collection('users').doc(userId);
          userDoc.ref.get().then(user => {
            let points = user.data().points
            userDoc.update({
              points: ++points
            });
          });
          this.createVote({
            choiceKey: choiceKey,
            pollKey: pollKey,
            userKey: userId
          });
        }
      });
    });
  }

  decrementChoice(choiceKey) {
    const choiceDoc:any = this.db.collection('choices').doc(choiceKey);
    const userId = this.afAuth.auth.currentUser.uid;
    choiceDoc.ref.get().then(choice => {
      let pollKey = choice.data().poll
      let votes = choice.data().votes
      choiceDoc.update({
        votes: --votes
      });
      const userDoc:any = this.db.collection('users').doc(userId);
      userDoc.ref.get().then(user => {
        let points = user.data().points
        userDoc.update({
          points: --points
        });
      });
      // Find & delete vote
      this.db.collection('votes').snapshotChanges().pipe(first()).subscribe((votes:any) => {
        let filteredVote = votes.filter((vote) => {
          const searchedPollKey = vote.payload.doc._document.proto.fields.poll.stringValue;
          const searchedChoiceKey = vote.payload.doc._document.proto.fields.choice.stringValue;
          const searchedUserKey = vote.payload.doc._document.proto.fields.user.stringValue;
          return (searchedPollKey == pollKey && searchedChoiceKey == choiceKey && searchedUserKey == userId);
        });
        this.deleteVote(filteredVote[0].payload.doc.id);
      });
    });
  }


  createVote(value) {
    this.db.collection('votes').add({
      choice: value.choiceKey,
      poll: value.pollKey,
      user: value.userKey
    }).then(vote => {
      console.log("Vote created successfully", vote);
    }).catch(err => {
      console.log("Error creating vote", err);  
    });
  }

  deleteVote(voteKey) {
    this.db.collection('votes').doc(voteKey).delete().then((vote) => {
      console.log("Vote deleted successfully");
    }).catch(err => {
      console.log("Error deleting vote", err);
    });
  }

  getVotes(choiceKey) {
    return this.db.collection('votes', ref => ref.where('choice', '==', choiceKey)).snapshotChanges().pipe(first());
  }

}
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.