How to Build a Quiz App with Angular and Firebase

In this tutorial, we will learn how to build a quiz app using Angular and Firebase. The app will allow users to take quizzes, view their scores, and create and manage quizzes as well. We will be using Angular for the front-end and Firebase as our backend, which will handle user authentication and data storage.

Prerequisites

To follow along with this tutorial, you will need to have the following installed on your machine:

  • Node.js and npm (Node Package Manager)
  • Angular CLI (Command Line Interface)
  • Firebase account

Step 1: Set Up Angular Project

Let’s start by setting up our Angular project. Open your terminal and run the following command to install Angular CLI globally:

npm install -g @angular/cli

Next, create a new Angular project by running the following command:

ng new quiz-app

Navigate to the project directory:

cd quiz-app

Step 2: Set Up Firebase Project

Now, let’s set up our Firebase project. Go to the Firebase console and create a new project. Give it a name and select your preferred region.

Once your project is created, click on the “Authentication” tab in the sidebar, and enable the “Email/Password” sign-in method.

Next, click on the “Database” tab in the sidebar, and create a new Cloud Firestore database. Choose the “Start in test mode” option for now.

After creating the database, click on the “Rules” tab and replace the existing rules with the following:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

Step 3: Create the Quiz and Authentication Services

Let’s create two services: quiz.service.ts for managing quizzes and auth.service.ts for authentication.

Create a new directory called services under the src/app directory, and inside it, create quiz.service.ts and auth.service.ts files.

In quiz.service.ts, add the following code:

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class QuizService {
  private quizCollection: AngularFirestoreCollection<any>;

  constructor(private firestore: AngularFirestore) {
    this.quizCollection = this.firestore.collection<any>('quizzes');
  }

  createQuiz(quiz: any): Promise<any> {
    return this.quizCollection.add(quiz);
  }

  getQuizzes(): Observable<any[]> {
    return this.quizCollection.valueChanges({ idField: 'id' });
  }
}

In auth.service.ts, add the following code:

import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  user$: Observable<firebase.User | null>;

  constructor(public auth: AngularFireAuth) {
    this.user$ = this.auth.authState;
  }

  signUp(email: string, password: string): Promise<any> {
    return this.auth.createUserWithEmailAndPassword(email, password);
  }

  signIn(email: string, password: string): Promise<any> {
    return this.auth.signInWithEmailAndPassword(email, password);
  }

  signOut(): Promise<void> {
    return this.auth.signOut();
  }
}

Ensure that you have installed the angular/fire package by running the following command:

ng add @angular/fire

Step 4: Create the Quiz List Component

Now, let’s create the quiz list component which will display a list of available quizzes.

Generate a new component called quiz-list using the Angular CLI:

ng generate component quiz-list

In the quiz-list.component.ts file, add the following code:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { QuizService } from '../services/quiz.service';

@Component({
  selector: 'app-quiz-list',
  templateUrl: './quiz-list.component.html',
  styleUrls: ['./quiz-list.component.css']
})
export class QuizListComponent implements OnInit {
  quizzes$: Observable<any[]>;

  constructor(private quizService: QuizService) { }

  ngOnInit() {
    this.quizzes$ = this.quizService.getQuizzes();
  }
}

In the quiz-list.component.html file, add the following code:

<div *ngFor="let quiz of quizzes$ | async">
  <h2>{{ quiz.name }}</h2>
  <p>{{ quiz.description }}</p>
</div>

Step 5: Create the Quiz Detail Component

Let’s create the quiz detail component which will display the details of a specific quiz.

Generate a new component called quiz-detail using the Angular CLI:

ng generate component quiz-detail

In the quiz-detail.component.ts file, add the following code:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-quiz-detail',
  templateUrl: './quiz-detail.component.html',
  styleUrls: ['./quiz-detail.component.css']
})
export class QuizDetailComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

In the quiz-detail.component.html file, add the following code:

<h2>Quiz Detail</h2>

Step 6: Set Up Routing

Now, let’s set up the routing for our app. Open the app-routing.module.ts file and replace the existing code with the following:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { QuizListComponent } from './quiz-list/quiz-list.component';
import { QuizDetailComponent } from './quiz-detail/quiz-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/quizzes', pathMatch: 'full' },
  { path: 'quizzes', component: QuizListComponent },
  { path: 'quiz/:id', component: QuizDetailComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Step 7: Update App Component and HTML

Open the app.component.ts file and replace the existing code with the following:

import { Component } from '@angular/core';
import { AuthService } from './services/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(public authService: AuthService) { }
}

Open the app.component.html file and replace the existing code with the following:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="/">Quiz App</a>

  <ul class="navbar-nav ml-auto">
    <li class="nav-item" *ngIf="!authService.user$ | async">
      <a class="nav-link" routerLink="/signin">Sign in</a>
    </li>
    <li class="nav-item" *ngIf="!authService.user$ | async">
      <a class="nav-link" routerLink="/signup">Sign up</a>
    </li>
    <li class="nav-item" *ngIf="authService.user$ | async">
      <a class="nav-link" (click)="authService.signOut()">Sign out</a>
    </li>
  </ul>
</nav>

<div class="container mt-4">
  <router-outlet></router-outlet>
</div>

Step 8: Create the Signin and Signup Components

Let’s create the signin and signup components which will handle user authentication.

Generate a new component called signin using the Angular CLI:

ng generate component signin

Generate a new component called signup using the Angular CLI:

ng generate component signup

Add the routing for the signin and signup components in the app-routing.module.ts file:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { QuizListComponent } from './quiz-list/quiz-list.component';
import { QuizDetailComponent } from './quiz-detail/quiz-detail.component';
import { SigninComponent } from './signin/signin.component';
import { SignupComponent } from './signup/signup.component';

const routes: Routes = [
  { path: '', redirectTo: '/quizzes', pathMatch: 'full' },
  { path: 'quizzes', component: QuizListComponent },
  { path: 'quiz/:id', component: QuizDetailComponent },
  { path: 'signin', component: SigninComponent },
  { path: 'signup', component: SignupComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In the signin.component.ts file, add the following code:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-signin',
  templateUrl: './signin.component.html',
  styleUrls: ['./signin.component.css']
})
export class SigninComponent implements OnInit {
  email: string;
  password: string;
  error: string;

  constructor(private authService: AuthService, private router: Router) { }

  ngOnInit() {
  }

  signIn() {
    this.authService.signIn(this.email, this.password)
      .then(() => this.router.navigate(['/']))
      .catch(error => this.error = error.message);
  }
}

In the signin.component.html file, add the following code:

<h2>Sign In</h2>

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" class="form-control" id="email" [(ngModel)]="email">
</div>

<div class="form-group">
  <label for="password">Password</label>
  <input type="password" class="form-control" id="password" [(ngModel)]="password">
</div>

<div class="form-group">
  <button class="btn btn-primary" (click)="signIn()">Sign In</button>
</div>

<p *ngIf="error" class="text-danger">{{ error }}</p>

In the signup.component.ts file, add the following code:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.css']
})
export class SignupComponent implements OnInit {
  email: string;
  password: string;
  error: string;

  constructor(private authService: AuthService, private router: Router) { }

  ngOnInit() {
  }

  signUp() {
    this.authService.signUp(this.email, this.password)
      .then(() => this.router.navigate(['/']))
      .catch(error => this.error = error.message);
  }
}

In the signup.component.html file, add the following code:

<h2>Sign Up</h2>

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" class="form-control" id="email" [(ngModel)]="email">
</div>

<div class="form-group">
  <label for="password">Password</label>
  <input type="password" class="form-control" id="password" [(ngModel)]="password">
</div>

<div class="form-group">
  <button class="btn btn-primary" (click)="signUp()">Sign Up</button>
</div>

<p *ngIf="error" class="text-danger">{{ error }}</p>

Step 9: Deploy Firebase Hosting

Now that we have completed the app, let’s deploy it using Firebase Hosting.

First, install the Firebase CLI by running the following command:

npm install -g firebase-tools

Then, log in to Firebase by running the following command and following the authentication prompts:

firebase login

Next, initialize Firebase in your project directory by running the following command:

firebase init

Select “Hosting” as the Firebase CLI feature, and choose the Firebase project you created earlier. For the public directory, enter “dist/quiz-app” (or the directory where the build artifacts are located).

After the initialization is complete, build the Angular app by running the following command:

ng build --prod

Finally, deploy the app to Firebase Hosting by running the following command:

firebase deploy

Congratulations! Your quiz app is now deployed and accessible using the provided Firebase Hosting URL.

Conclusion

In this tutorial, we learned how to build a quiz app using Angular and Firebase. We set up the Angular project, created services for managing quizzes and authentication, and built components for the quiz list, quiz detail, signin, and signup functionality. We also deployed our app using Firebase Hosting, making it available to users on the web.

Feel free to enhance the app by adding more features, such as the ability for users to answer quizzes and view their scores. You can also explore other Angular and Firebase features, such as Firestore transactions for handling quiz submissions and Cloud Functions for server-side logic. Happy coding!

Related Post