I've build an Angular 8 quiz application with an ASP.NET Core 3 backend. Bear with me as I am pretty new to Angular. The app registers users, logs them in, allows them to create quizzes and add and edit questions related to quizzes. Right now, when they take a quiz and click the 'Submit' button, a dialog pops up with their correct Answers / total questions, see below. The code for that dialog is below as well.
playquiz.component.html (left out some code for brevity, notice the Finish Quiz button)
<mat-list *ngFor="let question of pagedList">
<span>Question {{pageIndex + 1}}:</span><mat-list-item class="clickLink" [innerHTML]="question.questionText"></mat-list-item>
<mat-radio-group [(ngModel)]="question.selectedAnswer">
<mat-radio-button appearance="outline" class="fullWidth" *ngFor="let answer of question.answers" [value]="answer">
<span [innerHTML]="answer"></span>
</mat-radio-button>
</mat-radio-group><br /><br />
<button *ngIf="pageIndex == length - 1" mat-button color="primary" (click)="finish()">Finish Quiz</button>
<button *ngIf="pageIndex == length - 1" mat-button color="primary" [routerLink]="['/quizzes', quiz.quizId]">Post Quiz</button>
</mat-list>
playquiz.component.ts
import { Component } from '@angular/core'
import { ApiService } from './api.service'
import { ActivatedRoute } from '@angular/router'
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import { FinishedComponent } from './finished.component'
import {PageEvent} from '@angular/material';
@Component({
templateUrl: './playQuiz.component.html'
})
export class PlayQuizComponent {
constructor(private api: ApiService, private route: ActivatedRoute, private dialog: MatDialog) {}
quizId
questions
quizAttempt = {}
pagedList = []
length = 0
pageSize = 1
pageIndex = 0
ngOnInit(){
this.quizId = this.route.snapshot.paramMap.get('quizId')
this.api.getQuestions(this.quizId).subscribe(res => {
this.questions = res
//first, create Answers list
this.questions.forEach(q => {
q.answers = [ q.correctAnswer, q.answer1, q.answer2, q.answer3 ]
// Then, shuffle the answers array
shuffle(q.answers)
});
this.pagedList = this.questions.slice(0, this.pageSize);
this.length = this.questions.length;
})
}
// for Submit button, tally the correct answers and score the quiz
finish(){
var correct = 0;
this.questions.forEach(q => {
if (q.correctAnswer == q.selectedAnswer)
correct++
});
const dialogRef = this.dialog.open(FinishedComponent, {
data: { correct, total: this.questions.length}
});
console.log(correct)
}
OnPageChange(event: PageEvent){
let startIndex = event.pageIndex * event.pageSize;
let endIndex = startIndex + event.pageSize;
if(endIndex > this.length){
endIndex = this.length;
}
this.pagedList = this.questions.slice(startIndex, endIndex);
this.pageIndex = event.pageIndex
}
post(quizAttempt){
quizAttempt.quizId = this.quizId
quizAttempt.correctAnswers = 5 // hard-coded for testing
quizAttempt.attemptDate = Date.now()
this.api.postQuizAttempt(quizAttempt)
}
}
// Used ES6 version - https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle(a){
for (let i = a.length; i; i--){
let j = Math.floor(Math.random() * i);
[a[i - 1], a[j]] = [a[j], a[i - 1]];
}
}
api.service.ts
postQuizAttempt(quizAttempt){
this.http.post(`http://localhost:21031/api/quizzes/${quizAttempt.quizId}`, quizAttempt).subscribe(res => {
console.log(res)
})
}
app.module.ts (routes)
const routes = [
{ path: '', component: HomeComponent },
{ path: 'question', component: QuestionComponent },
{ path: 'question/:quizId', component: QuestionComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'quiz', component: QuizComponent },
{ path: 'play', component: PlayComponent },
{ path: 'playQuiz/:quizId', component: PlayQuizComponent },
{ path: 'quizzes/:quizId', component: PlayQuizComponent }
]
In addition to displaying the user's quiz score, I want to store their "attempt" in the database. I created a model in ASP.NET Core backend called QuizAttempt.cs with the properties I need to store, and in turn I used EF Core to create a table called QuizAttempts. I created an endpoint in my QuizzesController.cs. That code is below. I've tested the backend endpoint in Postman and it is working and storing the data correctly in the database.
I've tried passing the URL parameter in Angular (see above in the app.module.ts and the second button on playQuiz.component.html), like I'm doing with the questions component, but I keep getting 'cannot get property quizId from undefined' even though the quizId parameter is clearly in the URL and it is in the console.log. I'm not sure what to add to my finish() function to get it to store the quizAttempt in the database. I've added the endpoint to my api.service.ts in Angular, see above. I don't understand what I'm missing. I'm stuck, please help! (Please go easy on me, as I am a newbie in Angular and trying to learn.)
QuizzesController.cs
[Authorize]
[HttpPost("{id}")]
public async Task<ActionResult<QuizAttempt>> PostQuizAttempt(int id, QuizAttempt quizAttempt)
{
var userId = HttpContext.User.Claims.First().Value;
quizAttempt.UserId = userId;
var quiz = await _context.Quiz.FindAsync(id);
quizAttempt.QuizId = quiz.QuizId;
_context.QuizAttempts.Add(quizAttempt);
await _context.SaveChangesAsync();
return CreatedAtAction("GetQuizAttempt", new { id = quizAttempt.Id }, quizAttempt);
}
QuizAttempt.cs
public class QuizAttempt
{
public int Id { get; set; }
public string UserId { get; set; }
public int QuizId { get; set; }
public DateTime AttemptDate { get; set; }
public int CorrectAnswers { get; set; }
}