Angular 9 Tutorial: Creating Firebase Chat Web App

by Didin J. on Jun 11, 2020 Angular 9 Tutorial: Creating Firebase Chat Web App

A comprehensive step by step Angular 9 tutorial on creating Firebase chat web app with the quick user login, room list, and chat room with online users list

In this Angular 9 tutorial, we will gonna show you a chat web app that uses Firebase Realtime-Database. This chat web app has the features of quick user login and creation by the nickname, room list, and chat room with the online user's list. The flow is very simple as described in this diagram.

Angular 9 Tutorial: Creating Firebase Chat Web App - flow diagram

The first entry of this Angular 9 application is the login page. In the login page, it will check if the nickname exists in the local storage. If the nickname exists, it will go to the room list directly, and if not exists, it will stay on the login page to enter the nickname in the login form. The request from the login form will compare with the user's document in the Firebase Realtime-Database. If the nickname exists in the Firebase document then it will go to the room list and set the nickname to the local storage. If the nickname does not exist in the Firebase then it will save the nickname first to the users' document before going to the room list and set to the local storage. The room list contains a list of the chat room, a current user nickname, add the room button, and a logout button. The add button action will go to the add room form then compare the submitted room name to the Firebase realtime-database document. If the room name exists, it will return an alert, and if not exists, it will save a new room name to Firebase document. The new room will automatically appear on the room list. The logout button action will log out and exit the room list by removing the nickname from the local storage and redirect the page to the login page. Each room item in the room list has an action to go to the chat room. In the chat room, there is a chat box contain the message, a message form, the online user's lists, and an exit button that have action to go back to the room list.

This tutorial divided into several steps:

The following tools, frameworks, libraries, and modules are required for this tutorial:

  1. Node.js and NPM
  2. Angular 9 CLI
  3. Angular Material
  4. Firebase Module
  5. Google Firebase
  6. Terminal (Mac/Linux) or Node Command Line (Windows)
  7. IDE or Text Editor (We are using Visual Studio Code)

You can watch the video tutorial from our YouTube channel. If you like it, please like, share, comment, and subscribe to this channel.

Let get started to the main steps!


Step #1: Setup Firebase Realtime-Database

We will set up or create a new Google Firebase project that can use the realtime database. Just open your browser then go to Google Firebase Console and you will take to this page.

Angular 9 Tutorial: Creating Firebase Chat Web App - firebase welcome

From that page, click the Create Project button to create a Google Firebase project then it will be redirected to this page.

Angular 9 Tutorial: Creating Firebase Chat Web App  - Project name

After filling the project name text field which our project name is "AngularChat" and select your parent resources to your domain (ours: "djamware.com") then click the continue button and it will be redirected to this page.

Angular 9 Tutorial: Creating Firebase Chat Web App - Firebase analytics

This time, choose to not add Firebase analytics for now then click Create Project button. Now, you have a Google Firebase Project ready to use.

Angular 9 Tutorial: Creating Firebase Chat Web App - Firebase ready

After clicking the Continue button it will be redirected to this page.

Angular 9 Tutorial: Creating Firebase Chat Web App - project dashboard

Choose the Database menu in the left pane then click the Create Database button.

Angular 9 Tutorial: Creating Firebase Chat Web App - create database

Select "Start in test mode" then click next and it will go to the next dialog.

Angular 9 Tutorial: Creating Firebase Chat Web App - database location

Select the Firebase database server location (better near your Angular server location) then click the Done button. Don't forget to select or change Cloud Firestore to Realtime Database in Develop -> Database dashboard. Next, go to the Rules tab and you will see these rules values.

{
  /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
  "rules": {
    ".read": false,
    ".write": false
  }
}

Change it to readable and writeable from everywhere for this tutorial only.

{
  /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
  "rules": {
    ".read": "auth === null",
    ".write": "auth === null"
  }
}

Click the publish button to update or save the changes. Now, the Google Firebase realtime database is ready to use with your Angular 9 web app.


Step #2: Create a New Angular 9 App

Before update or install the Angular 9 CLI, make sure, you have installed the latest Node.js and NPM. Type this command to check the version of Node.js and NPM.

node -v
v12.18.0
npm -v
6.14.5

Next, type this command to update or install the Angular 9 CLI.

sudo npm install -g @angular/cli

Now, we have an @angular/cli version 9.1.7 in your terminal or Node environment. Next, create a new Angular 9 app inside your Angular applications folder.

ng new angular-chat

For now, choose "y" to add Angular routing and leave other questions as default by press the Enter key. Next, go to the newly created Angular 9 application then open it with your Text Editor or IDE. To use VSCode, just type this command in this new Angular 9 app folder.

cd ./angular-chat
code .

Now, the new Angular 9 app ready to develop.


Step #3: Install and Configure Firebase SDK

We will use Google Firebase Javascript SDK for accessing Firebase Realtime Database. For that, type this command to install the module.

npm install --save firebase

Next, register the Firebase SDK module in the Angular Maps app by open and edit this file `src/app/app.component.ts` then add these imports of Firebase.

import * as firebase from 'firebase';

Declare a constant variable for holds Firebase setting before `@Component` that contains the configuration variable for accessing Firebase using apiKey and databaseURL.

const config = {
  apiKey: 'YOUR_API_KEY',
  databaseURL: 'YOUR_DATABASE_URL'
};

You can find API Key in the Firebase console under Settings(gear icon) and there is Web API Key. For databaseURL, go to the Service Accounts tab under Settings, and you will see databaseURL in Admin SDK configuration snippet for Node.js. Next, initialize Firebase configuration settings inside Angular Component constructor.

constructor() {
  firebase.initializeApp(config);
}

Now, the Firebase realtime database is ready to use.


Step #4: Implementing Login

We will use a new component or page to implementing a login form which it's only a text field of the nickname and a login button. For that, add the new components for the required pages by type these commands.

ng g component login
ng g component roomlist
ng g component addroom
ng g component chatroom

Next, we will register all of those components to the routing. Open and edit "src/app/app-routing.module.ts" then replace the routes constant with this.

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'roomlist/:nickname', component: RoomlistComponent },
  { path: 'addroom', component: AddroomComponent },
  { path: 'chatroom/:nickname/:roomid', component: ChatroomComponent },
  { path: '',
    redirectTo: '/login',
    pathMatch: 'full'
  }
];

Next, open and edit "src/app/app.component.html" then replace all HTML tags with this.

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

Open and edit "src/app/app.component.css" then add these lines of CSS codes.

.container {
  padding: 20px;
}

For the input-form, we will use Angular Material and Reactive Form. For that, add Angular Material to this project using schematics.

ng add @angular/material

If there are the questions, just leave them as default by pressing the Enter key. Next, open and edit `src/app/app.module.ts` then add these imports of the required Angular Material components and Angular ReactiveForm.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatCardModule } from '@angular/material/card';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSortModule } from '@angular/material/sort';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { DatePipe } from '@angular/common';

Add them into @NgModule imports.

  imports: [
    ...
    FormsModule,
    ReactiveFormsModule,
    MatInputModule,
    MatIconModule,
    MatCardModule,
    MatFormFieldModule,
    MatTableModule,
    MatProgressSpinnerModule,
    MatSortModule,
    MatSnackBarModule,
    MatSidenavModule
  ],

And add DatePipe to the @NgModule providers.

providers: [DatePipe],

Next, open and edit "src/app/login/login.component.ts" then add these lines of imports.

import { Router } from '@angular/router';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import * as firebase from 'firebase';

Add a class that implements the "ErrorStateMatcher" before the @Component.

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Declare all required variables at the top of the main class body.

  loginForm: FormGroup;
  nickname = '';
  ref = firebase.database().ref('users/');
  matcher = new MyErrorStateMatcher();

Inject the required Angular Router and FormBuilder module to the constructor.

constructor(private router: Router, private formBuilder: FormBuilder) { }

Add a condition to check if the nickname exists in the local storage and initialize the FormBuilder to the ngOnInit function.

  ngOnInit() {
    if (localStorage.getItem('nickname')) {
      this.router.navigate(['/roomlist']);
    }
    this.loginForm = this.formBuilder.group({
      'nickname' : [null, Validators.required]
    });
  }

Add a function to submit the login form to the Firebase realtime-database.

  onFormSubmit(form: any) {
    const login = form;
    this.ref.orderByChild('nickname').equalTo(login.nickname).once('value', snapshot => {
      if (snapshot.exists()) {
        localStorage.setItem('nickname', login.nickname);
        this.router.navigate(['/roomlist']);
      } else {
        const newUser = firebase.database().ref('users/').push();
        newUser.set(login);
        localStorage.setItem('nickname', login.nickname);
        this.router.navigate(['/roomlist']);
      }
    });
  }

Next, open and edit "src/app/login/login.component.html" then replace all HTML tags with this Angular Material Form.

<div class="container">
  <form class="example-form" [formGroup]="loginForm" (ngSubmit)="onFormSubmit(loginForm.value)">
      <h2>Please login using your nickname</h2>
      <mat-form-field class="example-full-width">
        <mat-label>Nickname</mat-label>
        <input matInput placeholder="Enter your nickname" formControlName="nickname"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('nickname').valid && loginForm.get('nickname').touched">Please enter your nickname</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
          <button type="submit" [disabled]="!loginForm.valid" mat-fab color="primary"><mat-icon>login</mat-icon></button>
      </div>
  </form>
</div>

Finally, adjust some style for this login form by adding these CSS codes to the "src/app/login/login.component.css".

.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}


Step #5: Implementing Room List and Add Room

We will implement the Room list that contains a list of rooms, add a room button, and a logout button. For that, open and edit "src/app/roomlist/roomlist.component.ts" then add these imports of Angular ActivatedRoute, Router, Firebase, and the DatePipe.

import { ActivatedRoute, Router } from '@angular/router';
import * as firebase from 'firebase';
import { DatePipe } from '@angular/common';

We use DatePipe to convert the Javascript Date to the string. Next, add this constant function before the @Component to extract or convert the Firebase response to the array of objects.

export const snapshotToArray = (snapshot: any) => {
  const returnArr = [];

  snapshot.forEach((childSnapshot: any) => {
      const item = childSnapshot.val();
      item.key = childSnapshot.key;
      returnArr.push(item);
  });

  return returnArr;
};

Declare the required variables at the top of the main class body.

  nickname = '';
  displayedColumns: string[] = ['roomname'];
  rooms = [];
  isLoadingResults = true;

Inject the imported modules and initialize the nickname from the local storage and implementing the request to the Firebase realtime-database that always executes every time the Firebase document is changed.

  constructor(private route: ActivatedRoute, private router: Router, public datepipe: DatePipe) {
    this.nickname = localStorage.getItem('nickname');
    firebase.database().ref('rooms/').on('value', resp => {
      this.rooms = [];
      this.rooms = snapshotToArray(resp);
      this.isLoadingResults = false;
    });
  }

Add a function to enter the chat room that triggers when users choose the room in the template.

  enterChatRoom(roomname: string) {
    const chat = { roomname: '', nickname: '', message: '', date: '', type: '' };
    chat.roomname = roomname;
    chat.nickname = this.nickname;
    chat.date = this.datepipe.transform(new Date(), 'dd/MM/yyyy HH:mm:ss');
    chat.message = `${this.nickname} enter the room`;
    chat.type = 'join';
    const newMessage = firebase.database().ref('chats/').push();
    newMessage.set(chat);

    firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(roomname).on('value', (resp: any) => {
      let roomuser = [];
      roomuser = snapshotToArray(resp);
      const user = roomuser.find(x => x.nickname === this.nickname);
      if (user !== undefined) {
        const userRef = firebase.database().ref('roomusers/' + user.key);
        userRef.update({status: 'online'});
      } else {
        const newroomuser = { roomname: '', nickname: '', status: '' };
        newroomuser.roomname = roomname;
        newroomuser.nickname = this.nickname;
        newroomuser.status = 'online';
        const newRoomUser = firebase.database().ref('roomusers/').push();
        newRoomUser.set(newroomuser);
      }
    });

    this.router.navigate(['/chatroom', roomname]);
  }

In that function, it will save a chat message that showed the user enters the chat room and add new users to the "roomusers" document in the Firebase realtime-database. Next, add a function to logout by removing the nickname from the local storage and redirect back to the login page.

  logout(): void {
    localStorage.removeItem('nickname');
    this.router.navigate(['/login']);
  }

Next, open and edit "src/app//roomlist/roomlist.component.html" then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <h3>{{nickname}} <button mat-flat-button (click)="logout()"><mat-icon>logout</mat-icon></button></h3>
  <h2>Room List</h2>
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <button mat-flat-button color="primary" [routerLink]="['/addroom']"><mat-icon>add</mat-icon></button>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="rooms" class="example-table"
           matSort matSortActive="roomname" matSortDisableClear matSortDirection="asc">

      <!-- Room Name Column -->
      <ng-container matColumnDef="roomname">
        <th mat-header-cell *matHeaderCellDef>Room Name</th>
        <td mat-cell *matCellDef="let row">{{row.roomname}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="enterChatRoom(row.roomname)"></tr>
    </table>
  </div>
</div>

Give the room list styles by adding these lines of CSS codes to the "src/app/roomlist/roomlist.component.css".

.example-container {
  position: relative;
  padding: 10px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.button-row {
  margin: 10px 0;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

To add a new room, we will use another component that will contain an Angular Material Form. Open and edit "src/app/addroom/addroom.component.ts" then add this line of imports.

import { Router, ActivatedRoute } from '@angular/router';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import * as firebase from 'firebase';

Add a class that implementing the "ErrorStateMatcher" before the @Component.

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Declare all required variables at the top of the class body.

  roomForm: FormGroup;
  nickname = '';
  roomname = '';
  ref = firebase.database().ref('rooms/');
  matcher = new MyErrorStateMatcher();

Inject those imported modules to the constructor.

  constructor(private router: Router,
              private route: ActivatedRoute,
              private formBuilder: FormBuilder,
              private snackBar: MatSnackBar) {}

Initialize the FormBuilder in the ngOnInit function.

  ngOnInit(): void {
    this.roomForm = this.formBuilder.group({
      'roomname' : [null, Validators.required]
    });
  }

Add a function to submit the Angular form.

  onFormSubmit(form: any) {
    const room = form;
    this.ref.orderByChild('roomname').equalTo(room.roomname).once('value', (snapshot: any) => {
      if (snapshot.exists()) {
        this.snackBar.open('Room name already exist!');
      } else {
        const newRoom = firebase.database().ref('rooms/').push();
        newRoom.set(room);
        this.router.navigate(['/roomlist']);
      }
    });
  }

Next, implementing the form template by open and edit "src/app/addroom/addroom.component.html" then replace all HTML tags with this.

<div class="container">
  <form class="example-form" [formGroup]="roomForm" (ngSubmit)="onFormSubmit(roomForm.value)">
      <h2>Please enter new Room</h2>
      <mat-form-field class="example-full-width">
        <mat-label>Room Name</mat-label>
        <input matInput placeholder="Enter room name" formControlName="roomname"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!roomForm.get('roomname').valid && roomForm.get('roomname').touched">Enter room name</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
          <button type="submit" [disabled]="!roomForm.valid" mat-fab color="primary"><mat-icon>save</mat-icon></button>
      </div>
  </form>
</div>

Finally, give that form styles by adding these CSS codes to the "src/app/addroom/addroom.component.css".

.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}


Step #6: Implementing Chat Room

Now, we will implement the main steps of this Angular 9 tutorial that is the Chat room which has the online users list, the chatbox, and a message form. We split the user list and the chatbox by using Angular Material Navigation Drawer (mat-drawer). Open and edit "src/app/chatroom/chatroom.component.ts" then add or modify these required imports.

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import * as firebase from 'firebase';
import { DatePipe } from '@angular/common';

Add a class that implementing the "ErrorStateMatcher" that required by chat message from before the @Component.

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Add this constant function before the @Component to extract or convert the Firebase response to the array of objects.

export const snapshotToArray = (snapshot: any) => {
  const returnArr = [];

  snapshot.forEach((childSnapshot: any) => {
      const item = childSnapshot.val();
      item.key = childSnapshot.key;
      returnArr.push(item);
  });

  return returnArr;
};

Add these variables to implementing an auto-scroll to the chat box inside the top of the main class body.

  @ViewChild('chatcontent') chatcontent: ElementRef;
  scrolltop: number = null;

Also, add these required variables of the chat list, user list, and message form.

  chatForm: FormGroup;
  nickname = '';
  roomname = '';
  message = '';
  users = [];
  chats = [];
  matcher = new MyErrorStateMatcher();

Inject the required imported modules to the constructor and get the nickname from the local storage, the room name from the router params, and get the users and chats list from the Firebase realtime-database documents.

  constructor(private router: Router,
              private route: ActivatedRoute,
              private formBuilder: FormBuilder,
              public datepipe: DatePipe) {
                this.nickname = localStorage.getItem('nickname');
                this.roomname = this.route.snapshot.params.roomname;
                firebase.database().ref('chats/').on('value', resp => {
                  this.chats = [];
                  this.chats = snapshotToArray(resp);
                  setTimeout(() => this.scrolltop = this.chatcontent.nativeElement.scrollHeight, 500);
                });
                firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(this.roomname).on('value', (resp2: any) => {
                  const roomusers = snapshotToArray(resp2);
                  this.users = roomusers.filter(x => x.status === 'online');
                });
              }

Initialize the Form group for the message-form inside the ngOnInit function.

  ngOnInit(): void {
    this.chatForm = this.formBuilder.group({
      'message' : [null, Validators.required]
    });
  }

Add a function to submit the message form and save it to the Firebase realtime-database document.

  onFormSubmit(form: any) {
    const chat = form;
    chat.roomname = this.roomname;
    chat.nickname = this.nickname;
    chat.date = this.datepipe.transform(new Date(), 'dd/MM/yyyy HH:mm:ss');
    chat.type = 'message';
    const newMessage = firebase.database().ref('chats/').push();
    newMessage.set(chat);
    this.chatForm = this.formBuilder.group({
      'message' : [null, Validators.required]
    });
  }

Add a function to exit the chat room which is to send the exit message to the Firebase realtime database, set the room user status, and go back to the room list.

  exitChat() {
    const chat = { roomname: '', nickname: '', message: '', date: '', type: '' };
    chat.roomname = this.roomname;
    chat.nickname = this.nickname;
    chat.date = this.datepipe.transform(new Date(), 'dd/MM/yyyy HH:mm:ss');
    chat.message = `${this.nickname} leave the room`;
    chat.type = 'exit';
    const newMessage = firebase.database().ref('chats/').push();
    newMessage.set(chat);

    firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(this.roomname).on('value', (resp: any) => {
      let roomuser = [];
      roomuser = snapshotToArray(resp);
      const user = roomuser.find(x => x.nickname === this.nickname);
      if (user !== undefined) {
        const userRef = firebase.database().ref('roomusers/' + user.key);
        userRef.update({status: 'offline'});
      }
    });

    this.router.navigate(['/roomlist']);
  }

Next, implementing this chat room in the template that contains the user list, chatbox, and message form. Open and edit "src/app/chatroom/chatroom.component.html" then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <mat-drawer-container class="drawer-container">
    <mat-drawer mode="side" opened class="left-drawer">
      <div class="users-pane">
        <mat-card class="users-card">
          <button type="button" mat-button matSuffix mat-icon-button aria-label="Exit" (click)="exitChat()">
            <mat-icon>logout</mat-icon>
          </button>
        </mat-card>
        <mat-card class="users-card" *ngFor="let user of users">
          <mat-icon>person</mat-icon> <span class="username">{{user.nickname}}</span>
        </mat-card>
      </div>
    </mat-drawer>
    <mat-drawer-content class="chat-pane">
      <div #chatcontent [scrollTop]="scrolltop" class="chat-content">
        <div class="message-box" *ngFor="let chat of chats">
          <div class="chat-status" text-center *ngIf="chat.type==='join'||chat.type==='exit';else message">
            <span class="chat-date">{{chat.date | date:'short'}}</span>
            <span class="chat-content-center">{{chat.message}}</span>
          </div>
          <ng-template #message>
            <div class="chat-message">
              <div class="right-bubble" [ngClass]="{'right-bubble': chat.nickname === nickname, 'left-bubble': chat.nickname !== nickname}">
                <span class="msg-name" *ngIf="chat.nickname === nickname">Me</span>
                <span class="msg-name" *ngIf="chat.nickname !== nickname">{{chat.nickname}}</span>
                <span class="msg-date"> at {{chat.date | date:'short'}}</span>
                <p text-wrap>{{chat.message}}</p>
              </div>
            </div>
          </ng-template>
        </div>
      </div>
      <footer class="sticky-footer">
        <form class="message-form" [formGroup]="chatForm" (ngSubmit)="onFormSubmit(chatForm.value)">
          <mat-form-field class="message-form-field">
            <input matInput placeholder="Enter message here" formControlName="message"
                    [errorStateMatcher]="matcher">
            <mat-error>
              <span *ngIf="!chatForm.get('message').valid && chatForm.get('message').touched">Enter your message</span>
            </mat-error>
            <button type="submit" [disabled]="!chatForm.valid" mat-button matSuffix mat-icon-button aria-label="Send">
              <mat-icon>send</mat-icon>
            </button>
          </mat-form-field>
        </form>
      </footer>
    </mat-drawer-content>
  </mat-drawer-container>
</div>

Finally, give that template style by adding these lines of CSS codes to the "src/app/chatroom/chatroom.component.css".

.example-container {
  display: flex;
  padding: 10px;
}

.drawer-container {
  position: absolute;
  right: 0;
  left: 0;
  bottom: 0;
  top: 0;
}

.left-drawer {
  width: 200px;
}

.users-pane {
  height: 500px;
}

.users-card {
  margin: 5px 20px;
}

.username {
  position: absolute;
  margin-top: 5px;
  margin-left: 10px;
}

footer.sticky-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  padding: 10px;
  background-color: #ffffff;
  border-top: solid 1px #efefef;
}

.message-form {
  margin-left: 200px;
}

.message-form-field {
  width: 94%;
  margin: 0 4% 0 2%;
}

.message-box {
  float: left;
  width: 98%;
  margin: 5px 0 0 2%;
}

.chat-message {
  width: 80%;
  min-height: 40px;
}

.chat-message .right-bubble {
  position: relative;
  background: #dcf8c6;
  border-top-left-radius: .4em;
  border-bottom-left-radius: .4em;
  border-bottom-right-radius: .4em;
  padding: 5px 10px 10px;
  left: 15%;
}

.chat-message .right-bubble span.msg-name {
  font-size: 12px;
  font-weight: bold;
  color: green;
}
.chat-message .right-bubble span.msg-date {
  font-size: 10px;
}

.chat-message .right-bubble:after {
  content: '';
  position: absolute;
  right: 0;
  top: 13px;
  width: 0;
  height: 0;
  border: 27px solid transparent;
  border-left-color: #dcf8c6;
  border-right: 0;
  border-top: 0;
  margin-top: -13.5px;
  margin-right: -27px;
}
.chat-message .left-bubble {
  position: relative;
  background: lightblue;
  border-top-right-radius: .4em;
  border-bottom-left-radius: .4em;
  border-bottom-right-radius: .4em;
  padding: 5px 10px 10px;
  left: 5%;
}
.chat-message .left-bubble span.msg-name {
  font-size: 12px;
  font-weight: bold;
  color: blue;
}
.chat-message .left-bubble span.msg-date {
  font-size: 10px;
}
.chat-message .left-bubble:after {
  content: '';
  position: absolute;
  left: 0;
  top: 13px;
  width: 0;
  height: 0;
  border: 27px solid transparent;
  border-right-color: lightblue;
  border-left: 0;
  border-top: 0;
  margin-top: -13.5px;
  margin-left: -27px;
}

.chat-message .chat-status {
  min-height: 49px;
}

.chat-message .chat-status .chat-date {
  display: block;
  font-size: 10px;
  font-style: italic;
  color: #fff;
  text-shadow: 0px -1px 0px #222, 0px 1px 0px #aaa;
  height: 15px;
  left: 10%;
  right:10%;
}

.chat-message .chat-status .chat-content-center {
  padding: 5px 10px;
  background-color: #e1e1f7;
  border-radius: 6px;
  font-size: 12px;
  color: #555;
  height: 34px;
  left: 10%;
  right:10%;
}

.chat-content {
  overflow-y: scroll;
  height: 600px;
}


Step #7: Run and Test Angular 9 Firebase Chat Web App

We will use a separate web browser to test this Angular 9 Firebase Chat Web App. Next, run the Angular 9 app by type this command.

ng serve --open

The default browser will open this application automatically. Next, open another web browser then go to "localhost:4200" and you will see this whole Angular 9 Firebase app like this.

Angular 9 Tutorial: Creating Firebase Chat Web App - demo 1
Angular 9 Tutorial: Creating Firebase Chat Web App - demo 2
Angular 9 Tutorial: Creating Firebase Chat Web App - demo 3
Angular 9 Tutorial: Creating Firebase Chat Web App - demo 4
Angular 9 Tutorial: Creating Firebase Chat Web App - demo 6

And you will see this Firebase realtime-database structure like this in the Firebase console.

Angular 9 Tutorial: Creating Firebase Chat Web App - db structure

That it's, the Angular 9 Tutorial: Creating Firebase Chat Web App. You can get the working source codes from our GitHub.

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:

Thanks!