Rendering card content on a separate route [Angular] - html

I am learning MEAN stack. I've deployed my application here. Right now If you click on any card i.e. Read more button, it will automatically take you to a division where contenets are displayed. But I want to show all that content on a separate route or page because now I am planning to provide some more useful options such as Like, Report, Upvote,Donwvote`, etc. So I made some changes. Please look at them.
articles.component.html
<div class="row mt-5">
<div class="col-md-4 mb-3" *ngFor="let article of articles; let i = index;">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{article.title}}</h5>
<a (click)="onPress(i)" class="btn btn-primary">Read More</a>
</div>
<div class="card-footer text-muted">
{{article.date}}
</div>
</div>
</div>
</div>
articles.component.ts
import { Component, OnInit } from '#angular/core';
import { ArticlesService } from '../articles.service'; <---- SERVICE FOR READING FROM MONGODB
import {Router, ActivatedRoute }from '#angular/router';
#Component({
...
})
export class ArticlesComponent implements OnInit {
articles=[]
constructor(private _articleService: ArticlesService, private router: Router) { }
ngOnInit() {
this._articleService.getAllArticles()
.subscribe(
res => this.articles = res,
err => console.log(err)
)
}
onPress(id) {
console.log(id); <--- THIS PRINTS INDEX NO OF THE CARD
this.router.navigate(['/options',id]);
}
}
And all the options I mentioned above I've kept them in a separate component.
options-panel.component.html
<div style="margin: 0 auto; width:50vw">
<p> {{data }} </p>
<button (click)="back()"> Back</button>
<div style="display:flex; margin-top:1rem;padding:1rem;">
<button style="margin:0.5rem;"> upVote </button>
<button style="margin:0.5rem;"> DownVote </button>
...
</div>
</div>
options-panel.component.ts
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute,Router } from '#angular/router';
#Component({
...
})
export class OptionsPanelComponent implements OnInit {
private data;
constructor(private router: Router,private activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.activatedRoute.paramMap.subscribe(id =>{
this.data = id.get('id');
})
}
back(){
this.router.navigate(['../'])
}
}
And please review my app-routing.module.ts
...
import { ArticlesComponent } from './articles/articles.component';
import { OptionsPanelComponent } from './options-panel/options-panel.component';
const routes: Routes = [
{path: 'articles', component: ArticlesComponent},
{path:'',redirectTo: 'articles', pathMatch:'full'},
{path: 'options/:id', component:OptionsPanelComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
PS: I read documentation and dependancy injection in Angular. I also tried this stackblitz project.
But I'm getting no errors but blank page.
Please tell me what else I should do now.

What you showed in your picture was working, but you are printing just the id. You must get the article with that id from somewhere, like you service.
There is some information missing about your project, but you could make something like this.
Get the id from the route.
Retrieve your article from your service.
Show it on front using async if possible.
options-panel.component.ts
#Component({
...
})
export class OptionsPanelComponent implements OnInit {
private id: number;
private article$: Observable<Article>;
constructor(private router: Router, private activatedRoute: ActivatedRoute, private _articleService: ArticlesService) { }
ngOnInit() {
this.id = Number(this.activateRoute.snapshot.params['id']);
if (this.id) {
this.article$ = this._articleService.getArticle(this.id);
}
}
back(){
this.router.navigate(['../'])
}
}
options-panel.component.html
<div style="margin: 0 auto; width:50vw" *ngIf="article$ | async as article">
<p> {{ article.content }} </p>
<button (click)="back()"> Back</button>
<div style="display:flex; margin-top:1rem;padding:1rem;">
<button style="margin:0.5rem;"> upVote </button>
<button style="margin:0.5rem;"> DownVote </button>
...
</div>
</div>
If you are seeing that Welcome to my app! and logo, it must be on the index.html file.

If I am not wrong then, you want to create a separate link for each read more. In that case, you have to add router-link in your html and you have to update your routes as well. Try this one:
Your html:
<div class="row mt-5">
<div class="col-md-4 mb-3" *ngFor="let article of articles; let i = index;">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{article.title}}</h5>
<a [routerLink]="['/'+article.id]" routerLinkActive="router-link-active" (click)="onPress(i)" class="btn btn-primary">Read More</a>
</div>
<div class="card-footer text-muted">
{{article.date}}
</div>
</div>
</div>
</div>
this html will generate links for each read more. you also have to update your router and have to put your router-outlet in your desired position.
const routes: Routes = [
{
path: 'articles', component: ArticlesComponent,
children: [
{
path: 'id',
component: 'Read more component'
}
]
},
{ path: '', redirectTo: 'articles', pathMatch: 'full' },
{ path: 'options/:id', component: OptionsPanelComponent }
];

Related

How can I get a single element from a json?

I am creating a marketplace in angular and I have created the marketplace page which is populated by a remote json created with mockapi. The problem is that in the homepage I want to display a single item (possibly random) from the same json but with *ngFor it displays all the items.
This is my code:
export class DashboardComponent implements OnInit {
nfts: any;
constructor(
private http: HttpClient,
) {
}
ngOnInit(): void {
this.getNfts()
}
getNfts() {
this.http.get('https://63bd1526fa38d30d85d88179.mockapi.io/NFT/v1/metadata').subscribe((data) => {
this.nfts = data
})
}
}
// HTML
<div class="card cards card-p" *ngFor="let nft of nfts">
<img src="{{nft.image}}" class="card-img-top">
<div class="card-body">
<h4 class="nft-title">{{nft.name}}</h4>
<a class="nft-collection mb-3" routerLink="/">NFT collection</a>
<p>Price: <span>300</span></p>
<button class="button heart text-end"><i class="fa-solid fa-heart"></i></button>
<a routerLink="/nft-details/:id" class="stretched-link"></a>
</div>
</div>
I hope someone can help me! Thank you!
If all you want to do is display a single element (random) from an array then you can use something like "Math.floor(Math.random() * this.nfts.length)" to get a random element from the array and display that array.
so the html will look something like this
<div class="card cards card-p">
<img src="{{nfts[randomIndex].image}}" class="card-img-top">
<div class="card-body">
<h4 class="nft-title">{{nfts[randomIndex].name}}</h4>
<a class="nft-collection mb-3" routerLink="/">NFT collection</a>
<p>Price: <span>300</span></p>
<button class="button heart text-end"><i class="fa-solid fa-heart"></i></button>
<a routerLink="/nft-details/:id" class="stretched-link"></a>
</div>
</div>
The Javascript
export class DashboardComponent implements OnInit {
nfts: any;
randomIndex = 0;
constructor(
private http: HttpClient,
) {
}
ngOnInit(): void {
this.getNfts()
}
getNfts() {
this.http.get('https://63bd1526fa38d30d85d88179.mockapi.io/NFT/v1/metadata').subscribe((data) => {
this.nfts = data
this.randomIndex = Math.floor(Math.random() * this.nfts.length)
})
}
}
Use Object.map and iterate the elements that use the same syntax.

Best way to implement a pageable to Angular Project

I have products in my database and I have created controller on my backend app that I tested and works really good, so now I need to implement that to my frontend. I have product.component.ts file that looks like this
import { Component, OnInit } from "#angular/core";
import { FormControl, FormGroup } from "#angular/forms";
import { debounceTime, switchMap } from "rxjs";
import { ProductService } from "./product.service";
#Component({
selector: 'app-product',
templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit {
products: any[] = [];
productSearchForm!: FormGroup;
page: number = 0;
size: number = 4;
constructor(
private productService: ProductService
) { }
loadMore() {
this.page = this.page+1;
this.productSearchForm.get('searchTerm')?.valueChanges
.pipe(
debounceTime(500),
switchMap(value => {
return this.productService.searchByTermPageable(value, this.page, this.size);
})
).subscribe(data => {
this.products = data;
}, error => {
console.log(error);
// this.products = [];
});
}
ngOnInit(): void {
this.initializeForm();
this.productSearchForm.get('searchTerm')?.valueChanges
.pipe(
debounceTime(500),
switchMap(value => {
return this.productService.searchByTermPageable(value, this.page, this.size);
})
).subscribe(data => {
this.products = data;
}, error => {
console.log(error);
// this.products = [];
});
}
private initializeForm(): void {
this.productSearchForm = new FormGroup({
searchTerm: new FormControl(null)
});
}
}
searchTerm is a query param that is used to find products with name starting with that term. I call function from file product.service.ts that looks like this
import { HttpClient } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { Observable } from "rxjs";
import { environment } from "src/environments/environment";
#Injectable({ providedIn: 'root'})
export class ProductService {
constructor(private httpClient: HttpClient) { }
searchByTerm(searchTerm: string): Observable<any> {
const url = `${environment.apiUrl}product/search/by-name-or-desc?term=${searchTerm}`;
return this.httpClient.get(url);
}
searchByTermPageable(searchTerm: string, page: number, size: number): Observable<any> {
const url = `${environment.apiUrl}product/search/by-name-or-desc?term=${searchTerm}&page=${page}&size=${size}`;
return this.httpClient.get(url);
}
}
When I click the button load more I want to load next 4 products from database, but keep the first 4 products on my html page, so what is the best way to do this? This is my html page, and also I am using Bulma as my css framework
<div class="mt-5">
<form>
<div class="field" [formGroup]="productSearchForm">
<div class="control has-icons-left">
<input class="input" type="text" formControlName="searchTerm" placeholder="Find products">
<span class="icon is-small is-left">
<i class="fas fa-search"></i>
</span>
</div>
</div>
<hr />
</form>
</div>
<div class="columns">
<div class="column" *ngFor="let product of products">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4">{{ product.name }}</p>
<p class="subtitle is-6">{{ product.category?.name}}</p>
</div>
</div>
<div class="content">
{{ product.description }}
</div>
</div>
</div>
</div>
</div>
<button type="button" class="button is-primary" (click)="loadMore()">Load more...</button>
You can to have a new function in your service that return the elements of the page plus the other elements search. If you has a function that return the paginate elements
searchByTermPageable(search:string,page:number,size:number)
{
...
}
Some like
product:any[]=[] //define an empty array where you store the product
searchOld:string=null //and a variable to store the search
getData(search:string,page:number,size:number)
{
if (search!=this.searchOld) //if is a new search
{
this.searchOld=search
this.product=[]
}
return this.searchByTermPageable(search,page,size).pipe(
map((res:any[])=>[...this.product,...res]), //concat the before store product
tap((res:any[])=>{
this.product=res //store in the product the new array
})
)
}
Allow you some like
<div *ngFor="let product of product$|async">
{{product.name}}
</div>
<button (click)="more()">more</button>
pageIndex:number=-1;
product$:Observable<any[]>
more()
{
this.pageIndex++
this.product$=this.dataService.getData("",this.pageIndex,4)
}
The other solution is search all the product and simple use slice pipe
<div *ngFor="let product of allProduct$|async |slice:0:(index+1)*pageSize">
{{product.name}}
</div>
<button (click)="index=index+1">more</button>
a litle stackblitz

Displaying Data without *Ngfor

I'm trying to display data without using a ngFor loop. It works perfectly but shows all of the quote information from multiple customers. The CSS is laid out in a way that has the discount div next to the customerinfo div Here is the HTML
<hr />
<div class="info">
<div id="CustomerInfoInline" *ngIf="quotes" >
<div *ngFor="let q of quotes">
<h6>Name: {{q.firstName}} {{q.lastName}}</h6>
<h6>Address: {{q.address}}</h6>
<h6>City, State, Zip: {{q.city}}, {{q.state}}, {{q.zip}}</h6>
<h6>SSN: {{q.SSN}}</h6>
<h6>DOB: {{q.dateOfBirth}}</h6>
<h6>Email: {{q.email}}</h6>
<h6>Prev. Carrier: {{q.previousCarrier}}</h6>
<h1>-----------------------------------------------------------------------------------------------</h1>
</div>
</div>
<div *ngIf="quotes">
<div id="CustomerDiscountsInline" *ngFor="let q of quotes">
<h6 id="customerBold">Customer Discounts</h6>
<h4 id="DiscountsID">discounts will be applied here</h4>
</div>
</div>
</div>
<hr />
and the respective TS
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { environment } from '#environments/environment';
import { Quote } from '#app/_models/quote';
import { Router } from '#angular/router';
#Component({
selector: 'app-quote-summary',
templateUrl: './quote-summary.component.html',
styleUrls: ['./quote-summary.component.css']
})
export class QuoteSummaryComponent implements OnInit {
apiUrl: string = environment.apiUrl
quotes: Quote[]
//TODO: implement submitted quote view later
//submittedQuotes: Quote[]
constructor(private http: HttpClient, private router: Router) { }
ngOnInit(): void {
this.getQuotes()
}
// #region API Calls
getQuotes() {
var httpRequest = this.http.get<Quote[]>(`${this.apiUrl}/quotes`)
httpRequest.subscribe(returnedQuotes => {
this.quotes = returnedQuotes
})
}
}
If you need to show only one customer you can use indexer for quotes like quotes[0]:
Don't forgot to check quotes.length > 0:
<div class="info">
<div id="CustomerInfoInline"
<div *ngIf="quotes && quotes.length > 0">
<h6>Name: {{quotes[0].firstName}} {{quotes[0].lastName}}</h6>
<h6>Address: {{quotes[0].address}}</h6>
....
</div>
</div>
</div>

Angular 9 won't display image

I'm training with angular, i'm trying to display article's avatar that posted during registration, but image not appear correctly.
in the html :
<ng-container *ngIf="blogpost$ | async as bp; else loading">
<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
<img class="card-img-top" [src]="imagePath + bp[0].images">
<div class="card-header">{{ bp[0].title }}</div>
<div class="card-body">
<h5 class="card-title">{{ bp[0].subtitle }}</h5>
<p class="card-text">
{{ bp[0].content }}
</p>
</div>
<i class="material-icons" style="cursor: pointer;" [routerLink]="['edit']">
edit
</i>
</div>
</ng-container>
<ng-template #loading>Loading post ...</ng-template>
this [src]="imagePath + bp[0].images doesn't work
my imagePath variable is stored in environment.ts:
imagePath= 'http://localhost:3000/'(back-server)
The image doesn't appear and i've got this error "http://localhost:4200/undefined28-09-08_1609.jpg 404 (Not Found)".
[src]="bp[0].images" this doesn't work either.
article creation component:
import { Component, OnInit, ElementRef } from '#angular/core';
import { FormGroup, FormBuilder } from '#angular/forms';
import { BlogpostService } from '../blogpost-service';
import { Router } from '#angular/router';
#Component({
selector: 'app-blogpost-creation',
templateUrl: './blogpost-creation.component.html',
styleUrls: ['./blogpost-creation.component.css'],
})
export class BlogpostCreationComponent implements OnInit {
creationForm: FormGroup;
fileToUpload: File = null;
uploadPath: string = null;
constructor(private router: Router, private fb: FormBuilder, private blogPostService: BlogpostService) {}
ngOnInit() {
this.createForm();
}
createForm() {
this.creationForm = this.fb.group({
title: '',
subTitle: '',
content: '',
images: '',
});
}
createBlog() {
if (this.creationForm.valid) {
if (this.fileToUpload) {
this.blogPostService.uploadImage(this.fileToUpload).subscribe(
data => console.log('image', data),
error => console.log('error', error),
);
}
console.log('formGrp', this.creationForm);
this.blogPostService.createBlogPost(this.creationForm.value).subscribe(
data => console.log('DATA posted', data),
error => this.handleError(error),
);
if (this.creationForm.value) {
this.router.navigate(['']);
}
} else if (this.creationForm.valid) {
this.blogPostService.createBlogPost(this.creationForm.value).subscribe(
data => (this.fileToUpload = null),
error => this.handleError(error),
);
}
}
handleFileInput(event) {
this.fileToUpload = event.target.files[0];
console.log('uploaded file', this.fileToUpload);
}
handleSuccess(data) {
console.log('Post send', data);
}
handleError(error) {
console.log('Error when try to send post', error);
}
}
If someone have an idea to load an image! thank you :)
The error is pretty explanatory.
"http://localhost:4200/undefined28-09-08_1609.jpg 404 (Not Found)"
You defined imagePath in environment.ts but you also need to import that in your component.
When you write <img class="card-img-top" [src]="imagePath + bp[0].images">, Angular will look for imagePath defined in your component.
Following should solve your problem
import { environment } from 'environments/environment';
export class BlogpostCreationComponent implements OnInit {
imagePath = environment.imagePath;
}

Angular 6 Quiz Form: Bind and Save data between pages

My project contains 4 components: Course-List, Course-Detail, Course-Play, Course Quiz, and the scheme is like that:
The data is pass like that: every course have array of segment and every segments have array of questions. All the data I get from a backend server (Ruby on Rails API project) and it pass correctly.
This is the interfaces of each data structure:
export interface ICourse {
id: number;
title: string;
autor: string;
segments: ISegment[];
}
export interface ISegment {
id: number;
unit_id: number;
unit_title: string;
name: string;
type: string;
data: string;
questions: IQuestion[];
}
export interface IQuestion {
id: number;
question: string;
answer1: string;
answer2: string;
answer3: string;
answer4: string;
correct: number;
}
I'm having a problem with course-quiz component.
This is how the quiz looks:
Problems:
When click submit and next the data's not saving
I want to add option of coloring the correct answer and user answer after press submit
I want that when all questions are answered that new page show instead (not working probably because the first)
This is the project in stackblitz. The project not working because stackblitz isn't working with sass. I'll add relevant code also here:
app-routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { CourseListComponent } from './courses/course-list/course-list.component';
import { CourseDetailComponent } from './courses/course-detail/course-detail.component';
import { CoursePlayComponent } from './courses/course-play/course-play.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { CourseQuizComponent } from './courses/course-play/course-quiz/course-quiz.component';
// Routing array - set routes to each html page
const appRoutes: Routes = [
{ path: '', redirectTo: '/courses', pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses', component: CourseListComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses/:id', component: CourseDetailComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses/:id/segments/:id', component: CoursePlayComponent, pathMatch: 'full', runGuardsAndResolvers: 'always',
children: [{ path: 'questions/:id', component: CourseQuizComponent }]
},
{ path: '**', component: PageNotFoundComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' }];
#NgModule({
imports: [RouterModule.forRoot(appRoutes, { onSameUrlNavigation: 'reload' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
course-play.component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '#angular/router';
import { MatSidenavModule } from '#angular/material/sidenav';
import { LocalStorage } from '#ngx-pwa/local-storage';
import { DomSanitizer } from '#angular/platform-browser';
import { ICourse } from '../course';
import { ISegment } from '../course';
import { CourseService } from '../course.service';
// Couse-play decorator
#Component({
selector: 'lg-course-play-course-play',
templateUrl: './course-play.component.html',
styleUrls: ['./course-play.component.sass']
})
export class CoursePlayComponent implements OnInit {
errorMessage: string;
course: ICourse;
courseId: number;
public currentSegment: ISegment;
public showChildren: boolean = false;
constructor(private courseService: CourseService,
private route: ActivatedRoute,
private router: Router,
public sanitizer: DomSanitizer) { }
ngOnInit() {
// save this course id from course-detail and get http request from the service
this.courseId = JSON.parse(localStorage.getItem("courseId"))
this.getCourse(this.courseId);
}
// Get course detail by id
getCourse(id: number) {
this.courseService.getCourse(this.courseId).subscribe(
course => {
this.course = course;
// get the current segment id to use it on the html file
const id = +this.route.snapshot.paramMap.get('id');
this.getCurrentSegment(id);
},
error => this.errorMessage = <any>error);
}
// search in course single segment by id and save it in currentSegment
// to use it in the html file
getCurrentSegment(id: number){
this.currentSegment = this.course.segments.find(d => d.id === id);
}
changeShowChildren() {
this.showChildren = !this.showChildren;
}
}
course-play.component.html
<div class="row content" *ngIf="course">
<!-- Side nav-bar -->
<div class="col-md-3">
<!-- Image Logo -->
<div id="head_sidebar">
<img src="./assets/images/lg-white.png" class="d-inline-block align-top logo" alt="" routerLink="/courses" style="outline: none">
<h3>{{course.title}}</h3>
</div>
<div class="col-md-12 sidenav">
<!-- Menu elemets -->
<div class="nav nav-pills nav-stacked" *ngFor="let unit of course.segments | groupBy: 'unit_title'; let i = index">
<h6 class="course_play_title">Unit {{ i+1 }}: {{ unit.key }} </h6>
<ul>
<li class="course_play_item" *ngFor="let lesson of unit.value">
<a class="nav-link" [routerLink]="['/courses', course.id, 'segments', lesson.id]" (click)=getCurrentSegment(lesson.id)>{{lesson.name}}</a>
</li>
</ul>
</div>
</div>
</div>
<!-- Body -->
<div class="col-md-9 no-gutters" *ngIf="currentSegment">
<!-- Video Div -->
<div class="col-md-12 course_play_body text-center" *ngIf="currentSegment.segment_type === 'Video'">
<h1>{{currentSegment.name}}</h1>
<p class="small-text" *ngIf="course.segments?.length > 0">lesson {{currentSegment.id}} of {{course.segments?.length}}</p>
<hr>
<iframe frameborder="0" allowfullscreen="true" [src]='currentSegment.data | safe'></iframe>
<button type="button" class="prev btn btn-primary btn-lg" *ngIf="currentSegment.id > 1" [routerLink]="['/courses', course.id, 'segments', currentSegment.id-1]" (click)=getCurrentSegment(currentSegment.id-1)>Previous</button>
<button type="button" class="next btn btn-primary btn-lg" *ngIf="currentSegment.id < course.segments?.length" [routerLink]="['/courses', course.id, 'segments', currentSegment.id+1]" (click)=getCurrentSegment(currentSegment.id+1)>Next</button>
</div>
<!-- Quiz Div -->
<div class="col-md-12 course_play_body" *ngIf="currentSegment.segment_type === 'Quiz'">
<div class="col-md-12 course_play_body text-center" *ngIf="showChildren === false">
<h1>{{currentSegment.name}}</h1>
<p class="text-left"> Now that you've finished this unit, It's time to take a short quiz and see what you learned so far!
You'll need to choose one out of four answers which you think is correct.
After you've finished the quiz, you'll get your grade. feel free to re-take this quiz as much as you like.
Good Luck!
</p>
<p class="text-left big-text"> {{currentSegment.questions.length}} questions </p>
<a (click) = "showChildren = true"><h4>Start Quiz</h4></a>
<button style="margin-top: 30%" type="button" class="prev btn btn-primary btn-lg" *ngIf="currentSegment.id > 1" [routerLink]="['/courses', course.id, 'segments', currentSegment.id-1]" (click)=getCurrentSegment(currentSegment.id-1)>Previous</button>
<button style="margin-top: 30%" type="button" class="next btn btn-primary btn-lg" *ngIf="currentSegment.id < course.segments?.length" [routerLink]="['/courses', course.id, 'segments', currentSegment.id+1]" (click)=getCurrentSegment(currentSegment.id+1)>Next</button>
</div>
<quiz-course *ngIf="showChildren === true" [items]="currentSegment?.questions"></quiz-course>
</div>
</div>
course-quiz.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '#angular/router';
import { ICourse } from '../../course';
import { ISegment } from '../../course';
import { IQuestion } from '../../course';
import { CourseService } from '../../course.service';
import { PagerService } from '../../pager.service';
import * as _ from 'underscore';
#Component({
selector: 'quiz-course',
templateUrl: './course-quiz.component.html',
styleUrls: ['./course-quiz.component.sass']
})
export class CourseQuizComponent implements OnInit {
// the data I get from course-play component
#Input() items: IQuestion[];
// variables for pagination
pagedItems: IQuestion[];
pager: any = {};
public userAnswers = ['0', '0', '0', '0']; // array of user answers
public index: int; // index of userAnswers
public checkedAnswer: int; // the checked answer the user choose
public correctAnswer: boolean = false; // true if his answer correct, else false
public sum: int; // sum of the questions answered. needed to check if user finished quiz
constructor(private courseService: CourseService,
private route: ActivatedRoute,
private router: Router,
private pagerService: PagerService) { }
ngOnInit() {
this.setPage(1);
this.checkedAnswer = 0;
this.index = 0;
this.sum = 0;
}
// change pages
setPage(page: number) {
if (page < 1 || page > this.pager.totalPages) {
return;
}
// get pager object from service
this.pager = this.pagerService.getPager(this.items.length, page);
// get current page of items
this.pagedItems = this.items.slice(this.pager.startIndex, this.pager.endIndex + 1);
}
isChecked(value){
this.checkedAnswer = value;
}
// get value of the checked answer, check if it's correct and save it to the answer array
submitAnswer(correct) {
// if the user answer all the questions, go to finished page
if (sum == this.items.length) {
}
if (this.checkedAnswer == 0) {
// do something to notify user that an answer need to be checked
}
else if (this.checkedAnswer == correct) {
this.correctAnswer = true;
this.userAnswers[this.index] = "correct";
}
else {
this.correctAnswer = false;
this.userAnswers[this.index] = "incorrect";
}
this.index = this.index + 1;
this.sum = this.sum + 1;
}
}
course-quiz.component.html
<div class="container" *ngIf="sum < 4">
<div class="text-left quiz-body" *ngFor="let item of pagedItems">
<form>
<!-- items being paged -->
<h3>Question {{item.id}}/{{items.length}}</h3>
<h6>Question {{item.question}}</h6>
<ul class="items">
<li><input type="radio" id="answer1" name="answer" value="1" (click)="isChecked(1)"><label for="answer1">1. {{item.answer1}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="2" (click)="isChecked(2)"><label for="answer2">2. {{item.answer2}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="3" (click)="isChecked(3)"><label for="answer3">3. {{item.answer3}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="4" (click)="isChecked(4)"><label for="answer4">4. {{item.answer4}}</label></li>
</ul>
<button type="submit" class="btn btn-primary mb-2" (click)="submitAnswer(item.correct)">Submit</button>
<!-- Submit Buttom -->
<!-- pager -->
<ul *ngIf="pager.pages && pager.pages.length" class="pagination">
<li class="page-item" [ngClass]="{disabled:pager.currentPage === 1}">
<a class="page-link" (click)="setPage(1)">First</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === 1}">
<a class="page-link" (click)="setPage(pager.currentPage - 1)">Previous</a>
</li>
<li class="page-item" *ngFor="let page of pager.pages" [ngClass]="{active:pager.currentPage === page}">
<a class="page-link" (click)="setPage(page)">{{page}}</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === pager.totalPages}">
<a class="page-link" (click)="setPage(pager.currentPage + 1)">Next</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === pager.totalPages}">
<a class="page-link" (click)="setPage(pager.totalPages)">Last</a>
</li>
</ul>
</form>
</div>
</div>
<!-- If the user finished the quiz, this div will displaying instead -->
<div class="container" *ngIf="sum == 4">
<h3> You have just finished the quiz! </h3>
</div>