为什么没有正确预选投票选项?

Why are vote options not correctly pre-selected?

您需要经过身份验证才能投票。当您投票时,有 2 个问题:

  1. 您可以无限次投票,直到您离开或重新加载页面

  2. 当您重新加载页面时,您最终无法投票,但您投票的选项不会被预选,而总是预选第二个选项。


应该发生什么:

注册/登录,然后投票。在您点击一个选项的那一刻,您所做的民意调查投票选择被锁定,您不能对该民意调查进行更多投票。


我当前代码的工作流程:

当用户点击一个选项时,计数器增加,然后投票保存在一个数组中,该数组被推送到数据库中的用户对象。

加载组件时,当前登录用户的投票数据库数据通过本地 votes 变量内的 ngOninit() 方法保存,然后用于检查哪个投票用户已经投票以及他投了什么票。问题是当实际情况并非如此时,做出的选择总是 choice2。

我明白为什么你可以多次投票直到页面重新加载,但我只是不知道如何在用户投票后立即锁定投票,在客户端和后端(防止更多的选票被注册如果用户已经投票了)。

至于为什么已经是预选的第二选择了,我就不知道了。


代码:

HTML

<div class="formWidth">
    <form (ngSubmit)="onSubmit(f)" #f="ngForm">
        <div class="form-group">
            <label class="inputTitle" for="title">Poll Title</label>
            <input
                    type="text"
                    id="title"
                    class="form-control"
                    [ngModel]="poll?.title"
                    name="title"
                    required maxlenth="30">
            <label class="inputTitle" for="choice1">Choice1</label>
            <input
                    type="text"
                    id="choice1"
                    class="form-control"
                    [ngModel]="poll?.choice1"
                    name="choice1"
                    required maxlenth="20">
            <label class="inputTitle" for="choice2">Choice2</label>
            <input
                    type="text"
                    id="choice2"
                    class="form-control"
                    [ngModel]="poll?.choice2"
                    name="choice2"
                    required maxlenth="20">
        </div>
        <button type="button" class="btn btn-danger" (click)="onClear(f)">Clear</button>
        <button class="btn btn-primary" type="submit">Save</button>
    </form>
</div>

组件

export class PollComponent {
    @Input() poll: Poll;

    constructor(private pollService: PollService) {}

    votes: any;

    // Pie
    public pieChartLabels:string[] = [];
    public pieChartData:number[] = [];
    public pieChartType:string = 'pie';
    public pieChartOptions:any = {};

    ngOnInit() {
        var result1 = parseFloat(((this.poll.counter1/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
        var result2 = parseFloat(((this.poll.counter2/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
        this.pieChartData = [result1, result2];
        this.pieChartLabels = [this.poll.choice1, this.poll.choice2];
        this.pieChartType = 'pie';
        this.pieChartOptions  = {
            tooltips: {
            enabled: true,
            mode: 'single',
                callbacks: {
                    label: function(tooltipItem, data) {
                        var allData = data.datasets[tooltipItem.datasetIndex].data;
                        var tooltipLabel = data.labels[tooltipItem.index];
                        var tooltipData = allData[tooltipItem.index];
                        return tooltipLabel + ": " + tooltipData + "%";
                    }
                }
            }
        }

        this.pollService.voted(localStorage.getItem('userId')).subscribe(
            data => {
                var result = JSON.parse(data);
                this.votes = result.votes;
            },
            err => { console.log("NGONINIT ERROR: "+ err) },
            () => { }
        );
    }

    onEdit() {
        this.pollService.editPoll(this.poll);
    }

    onDelete() {
        this.pollService.deletePoll(this.poll)
            .subscribe(
                result => console.log(result)
            );
    }

    onChoice1() {
      this.pollService.increaseCounter1(this.poll);
      this.onVote1();
      var result1 = parseFloat(((this.poll.counter1/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
      var result2 = parseFloat(((this.poll.counter2/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
      this.pieChartData = [result1, result2];
    }

    onChoice2() {
      this.pollService.increaseCounter2(this.poll);
      this.onVote2();
      var result1 = parseFloat(((this.poll.counter1/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
      var result2 = parseFloat(((this.poll.counter2/(this.poll.counter2+this.poll.counter1))*100).toFixed(2));
      this.pieChartData = [result1, result2];
    }

    onVote1() {
      this.pollService.voteOn(this.poll.pollID,  localStorage.getItem('userId'), 1);
    }

    onVote2() {
      this.pollService.voteOn(this.poll.pollID,  localStorage.getItem('userId'), 2);
    }

    belongsToUser() {
        return localStorage.getItem('userId') == this.poll.userId;
    }

    alreadyVotedFor(choice: number) {
      var result = "";
      if (this.votes) {
          for (var i = 0; i < this.votes.length; i ++) {
              if (this.votes[i].pollID == this.poll.pollID) {
                  result = "disabled";
                  if (this.votes[i].choice == choice) {
                      result =  "selected";
                  }
              }
          }
      }
      return result;
    }

        // events
    public chartClicked(e:any):void {

    }

    public chartHovered(e:any):void {

    }

}

服务

updatePoll(poll: Poll) {
        const body = JSON.stringify(poll);
        const token = localStorage.getItem('token')
            ? localStorage.getItem('token')
            : '';
        const headers = new Headers({
          'Content-Type': 'application/json',
          'Authorization': 'Bearer '+token
        });
        return this.http.patch('https://voting-app-10.herokuapp.com/poll/' + poll.pollID, body, {headers: headers})
            .map((response: Response) => response.json())
            .catch((error: Response) => {
                this.errorService.handleError(error.json());
                return Observable.throw(error);
            });
    }

    increaseCounter1(poll: Poll) {
        poll.counter1++;
        const body = JSON.stringify(poll);
        const token = localStorage.getItem('token')
            ? localStorage.getItem('token')
            : '';
        const headers = new Headers({
          'Content-Type': 'application/json',
          'Authorization': 'Bearer '+token
        });
        this.http.patch('https://voting-app-10.herokuapp.com/poll/vote/' + poll.pollID, body, {headers: headers})
            .map((response: Response) => response.json())
            .catch((error: Response) => {
                this.errorService.handleError(error.json());
                return Observable.throw(error);
            })
            .subscribe();
    }

    increaseCounter2(poll: Poll) {
        poll.counter2++;
        const body = JSON.stringify(poll);
        const token = localStorage.getItem('token')
            ? localStorage.getItem('token')
            : '';
        const headers = new Headers({
          'Content-Type': 'application/json',
          'Authorization': 'Bearer '+token
        });
        return this.http.patch('https://voting-app-10.herokuapp.com/poll/vote/' + poll.pollID, body, {headers: headers})
            .map((response: Response) => response.json())
            .catch((error: Response) => {
                this.errorService.handleError(error.json());
                return Observable.throw(error);
            })
            .subscribe();
    }

    voteOn(pollID: string, userID: string, choice: number) {
      var user;
      this.http.get('https://voting-app-10.herokuapp.com/user/'+userID)
      .map(response => response.json())
      .subscribe(
            json => {
              user = JSON.parse(json);
              if (user.votes == undefined) {
                user.votes = [{pollID, choice}];
              } else {
                user.votes.push({pollID, choice});
              }
              const body = user;
              const token = localStorage.getItem('token')
                  ? localStorage.getItem('token')
                  : '';
              const headers = new Headers({
                'Content-Type': 'application/json',
                'Authorization': 'Bearer '+token
              });
              return this.http.patch('https://voting-app-10.herokuapp.com/user/', body, {headers: headers})
                  .map((response: Response) => response.json())
                  .catch((error: Response) => {
                      this.errorService.handleError(error.json());
                      return Observable.throw(error);
                  })
                  .subscribe();
            }
       )
    }

    voted(userID: string) {
        const headers = new Headers({'Content-Type': 'application/json'});
        return this.http.get('https://voting-app-10.herokuapp.com/user/'+userID,{headers: headers})
                        .map(response => response.json())
                        .catch((error: Response) => {
                            this.errorService.handleError(error.json());
                            return Observable.throw(error);
                        });
    }

路由(后端)

router.patch('/vote/:id', function (req, res, next) {
    var decoded = jwt.decode(getToken(req));
    Poll.findById(req.params.id, function (err, poll) {
        if (err) {
            return res.status(500).json({
                title: 'An error occurred',
                error: err
            });
        }
        if (!poll) {
            return res.status(500).json({
                title: 'No Poll Found!',
                error: {poll: 'Poll not found'}
            });
        }
        poll.title = req.body.title;
        poll.choice1 = req.body.choice1;
        poll.choice2 = req.body.choice2;
        poll.counter1 = req.body.counter1;
        poll.counter2 = req.body.counter2;

        poll.save(function (err, result) {
            if (err) {
                return res.status(500).json({
                    title: 'An error occurred',
                    error: err
                });
            }
            res.status(200).json({
                poll: 'Updated poll',
                obj: result
            });
        });
    });
});

router.patch('/:id', function (req, res, next) {
    var decoded = jwt.decode(getToken(req));
    Poll.findById(req.params.id, function (err, poll) {
        if (err) {
            return res.status(500).json({
                title: 'An error occurred',
                error: err
            });
        }
        if (!poll) {
            return res.status(500).json({
                title: 'No Poll Found!',
                error: {poll: 'Poll not found'}
            });
        }
        if (poll.user != decoded.user._id) {
            return res.status(401).json({
                title: 'Not Authenticated',
                error: {poll: 'Users do not match'}
            });
        }
        poll.title = req.body.title;
        poll.choice1 = req.body.choice1;
        poll.choice2 = req.body.choice2;
        poll.counter1 = req.body.counter1;
        poll.counter2 = req.body.counter2;

        poll.save(function (err, result) {
            if (err) {
                return res.status(500).json({
                    title: 'An error occurred',
                    error: err
                });
            }
            res.status(200).json({
                poll: 'Updated poll',
                obj: result
            });
        });
    });
});

好的,起初你的单选按钮没有被禁用,因为你在保存投票后没有更新 poll.component.ts 中的投票数组。

我不确定这是否是一个好的解决方案:

在你的poll.service.ts中:

voteOn(pollID: string, userID: string, choice: number) {
    var user;
    return new Promise((resolve) => { //Create a new promise to wrap the Subscriptions
    this.http.get('http://localhost:3000/user/' + userID)
        .map(response => response.json())
        .subscribe(
        json => {
            user = JSON.parse(json);
            if (user.votes == undefined) {

            ...

                .catch((error: Response) => {
                    this.errorService.handleError(error.json());
                    return Observable.throw(error);
                }).subscribe(() => {
                    resolve(user.votes); // <- Resolve your promise
                })
        }
        )
        });
}

在你的 poll.component.ts 中:

voteOn(...).then((votes) => {
    this.votes = votes; // To update your votes array
    this.updateVote();
})

并且我不建议在绑定中调用函数,因为碰巧这些函数被调用得非常频繁 "to detect changes",就像在您的组件中一样。 所以我会按以下方式更改代码:

在你的poll.component.ts中:

vote:any //Added to your poll component


updateVote() {
    this.vote = this.votes.find((vote) => {
        return vote.pollID === this.poll.pollID;
    });
}

您需要在您的 ngOnInit 方法中调用此方法:

    this.pollService.voted(localStorage.getItem('userId')).subscribe(
        data => {
            var result = JSON.parse(data);
            this.votes = result.votes;
            this.updateVote(); // <- To select the vote of this poll
        },
        err => { console.log("NGONINIT ERROR: " + err) },
        () => { }
    );

在你的poll.component.html中:

    <fieldset [disabled]="vote">
      {{ poll.counter1 }} votes <input type="radio" id="{{ poll.choice1 }}" name="my_radio" value="{{ poll.choice1 }}" (click)="onChoice1(form)" [checked]="vote?.choice == 1">  {{ poll.choice1 }}
      <br>
      {{ poll.counter2 }} votes <input type="radio" id="{{ poll.choice2  }}" name="my_radio" value="{{ poll.choice2 }}" (click)="onChoice2(form)" [checked]="vote?.choice == 2">  {{ poll.choice2 }}
    </fieldset>

但如果您不想以这种方式更改代码,请告诉我,以便我提供另一种解决方案。