The first thing we'll do is to implement a QuestionListComponent that will show a list of questions for any given quiz; once done, we'll add it to our already-existing QuizEditComponent, thus enabling our users to view, create, edit, and/or delete them from the same place where they can edit the details of the parent quiz.
Start with creating the /ClientApp/app/components/question/ folder, then right-click on it and add the question-list.component.ts file with the following content (relevant lines are highlighted):
import { Component, Inject, Input, OnChanges, SimpleChanges } from "@angular/core";
import { Router } from "@angular/router";
import { HttpClient } from "@angular/common/http";
@Component({
selector: "question-list",
templateUrl: './question-list.component.html',
styleUrls: ['./question-list.component.css']
})
export class QuestionListComponent implements OnChanges {
@Input() quiz: Quiz;
questions: Question[];
title: string;
constructor(private http: HttpClient,
@Inject('BASE_URL') private baseUrl: string,
private router: Router) {
this.questions = [];
}
ngOnChanges(changes: SimpleChanges) {
if (typeof changes['quiz'] !== "undefined") {
// retrieve the quiz variable change info
var change = changes['quiz'];
// only perform the task if the value has been changed
if (!change.isFirstChange()) {
// execute the Http request and retrieve the result
this.loadData();
}
}
}
loadData() {
var url = this.baseUrl + "api/question/All/" + this.quiz.Id;
this.http.get<Question[]>(url).subscribe(res => {
this.questions = res;
}, error => console.error(error));
}
onCreate() {
this.router.navigate(["/question/create", this.quiz.Id]);
}
onEdit(question: Question) {
this.router.navigate(["/question/edit", question.Id]);
}
onDelete(question: Question) {
if (confirm("Do you really want to delete this question?")) {
var url = this.baseUrl + "api/question/" + question.Id;
this.http
.delete(url)
.subscribe(res => {
console.log("Question " + question.Id + " has been
deleted.");
// refresh the question list
this.loadData();
}, error => console.log(error));
}
}
}
Once done, follow up with the question-list.component.html template file:
<h3>Questions</h3>
<div *ngIf="questions.length > 0">
<table class="questions">
<thead>
<tr>
<th>Text</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let question of questions">
<td>{{question.Text}}</td>
<td><input type="button" value="Edit"
(click)="onEdit(question)" />
<input type="button" value="Delete"
(click)="onDelete(question)" /></td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="questions.length == 0">
This quiz has no questions (yet):
click the <strong>Add a new Question</strong> button to add the first one!
</div>
<input type="button" value="Add a new Question" (click)="onCreate()" />
Also, follow up with the question.list.component.css style sheet file:
table.questions {
min-width: 500px;
}
We've got lot of new stuff here; let's see how it works.
By looking at the class implementation, we can see how we're expecting to receive a quiz property, which will most likely come from the parent component--the quiz-edit.component.ts file. Since it will come from there, we had to use the @Input() decorator to authorize the binding. The quiz property will be used within the loadData() method to assemble the URL for the server-side API to retrieve all the existing questions for the current quiz; as usual, that request will be issued by the Angular HttpClient, just like we did a number of times earlier.
However, it's difficult to miss that we're taking a different approach here. The news is the loadData() method itself, which is not called in the constructor phase, nor in the ngOnInit() life cycle hook we used in the past; we can clearly see that it's executed within the ngOnChanges() method, which is something we have never heard of.