我遇到了一个难以解决的问题。这两个问题似乎都是由比赛条件引起的。
1。)在完成this.poll.choices附加之前,将执行drawPoll()函数。我通过手动添加3秒setTimeout()来确认这是问题所在。如何确保drawPoll()函数仅在options.ForEach()迭代完成后才执行?
2。)在调用vote(choiceId)函数并减少一个选择时,firebaseService observable不会为“ votes”选择正确的值,因为observable在表决删除完成执行之前就加入了。如何重新排列我的代码,以便可观察的对象等到表决文档删除完成?
我试图将choices.forEach迭代包装在promise中,但是很难使它起作用。而且我不确定在哪里开始为decrementChoice()和getChoices()做一个诺言链,因为getChoices()函数在初始化时并不总是依赖于decrementChoice或crementChoice()函数。它仅取决于投票时的投票者。附件是我的组件和Firebase服务。任何帮助将不胜感激!
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 { first } from 'rxjs/operators';
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 = [];
username:string = "";
points:number;
@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
};
if (this.poll.pollType == 1) {
this.title = "Who Do I Start?";
}
if (this.poll.pollType == 2) {
this.title = "Who Do I Drop?";
}
if (this.poll.pollType == 3) {
this.title = "Who Do I Pick Up?";
}
if (this.poll.pollType == 4) {
this.title = "Who Wins This Trade?";
}
// Populate username and user points
this.firebaseService.getUser(pollData.user).subscribe((user:any) => {
const userDetails = user.payload._document.proto;
if (userDetails) {
this.username = userDetails.fields.username.stringValue;
this.points = userDetails.fields.points.integerValue;
}
});
this.firebaseService.getChoices(this.pollKey).pipe(first()).subscribe(choices => {
console.log("get 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).pipe(first()).subscribe((votes: any) => {
this.poll.choices.push({
id: choiceKey,
text: choiceData.text,
votes: votes.length
});
});
this.firebaseService.getVotes(choiceKey).subscribe((votes: any) => {
if (this.isDrawn) {
const selectedChoice = this.poll.choices.find((choice) => {
return choice.id == choiceKey
});
selectedChoice.votes = votes.length;
this.drawPoll();
}
});
});
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) {
console.log("text?", this.poll.choices.map(choice => choice.text));
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, 4, 40, 0.2)",
"rgba(19, 32, 98, 0.2)",
"rgba(255, 4, 40, 0.2)",
"rgba(19, 32, 98, 0.2)",
"rgba(255, 4, 40, 0.2)",
"rgba(19, 32, 98, 0.2)"
],
borderColor: [
"rgb(255, 4, 40)",
"rgb(19, 32, 98)",
"rgb(255, 4, 40)",
"rgb(19, 32, 98)",
"rgb(255, 4, 40)",
"rgb(19, 32, 98)",
],
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;
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;
});
if (checked) this.firebaseService.incrementChoice(choiceId);
if (!checked) this.firebaseService.decrementChoice(choiceId);
}
}
edit() {
this.editEvent.emit(this.poll);
}
delete() {
this.deleteEvent.emit(this.poll);
}
}
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());
}
}
**更新**
我能够通过为投票更新创建单独的订阅来解决问题2。该代码相当笨拙,但是至少现在#2不再是问题。我仍然遇到与#1相同的问题,其中在this.poll.choices完成迭代和追加之前已执行drawPoll()函数。我更新了问题以反映我更新的代码。