Creating a Chatroom in Angular using Amplication

Akshita Dixit
Akshita Dixit
Jul 20, 2023
Creating a Chatroom in Angular using Amplication
Creating a Chatroom in Angular using Amplication

Angular is a stunningly powerful web framework that combines design and development seamlessly, enabling developers to create captivating web applications with elegance and precision. Its extensive features and modular structure empower creativity and deliver exceptional user experiences.

Amplication is an open-source platform that simplifies the process of building backend servers for your applications. It provides developers with tools for building scalable, maintainable, and secure applications. Amplication generates a backend server based on a data model defined by the developer. It also supports GraphQL and REST APIs, allowing developers to create flexible and modern applications.

In this article, we will discuss how to create a chatroom in Angular using Amplication. We will cover the prerequisites, getting started, creating and utilizing the backend server, creating the Angular UI, and running the app.

Pre-requisites

Node.js and npm (Node Package Manager): Amplication generates a Node.js and NestJS code. To run your own server, you will need npm installed. Head over to download Node, which comes with npm from the given link https://nodejs.org/en/download/ Docker: The server created by Amplication will execute a Docker container running PostgreSQL that is required to develop or test a Node.js application. Docker is required to provide an isolated and reproducible environment for running the database service. Head over to the Docker and follow the steps to install it locally. You can find that here https://www.docker.com/get-started/.

🌟 Help us reach new heights! Give Amplication your star on GitHub! 🌟

Welcome to this exciting tutorial on creating a chatroom in Angular using Amplication! We're thrilled to embark on this journey together. But before we dive into the world of real-time communication, we have a special request for you. Just like stars illuminate the night sky, your stars can brighten our day and show your appreciation for the incredible work done by the Amplication team.

https://github.com/amplication/amplication


We invite you to visit the Amplication GitHub repository and give us your star. It's a simple yet powerful gesture that helps us gain recognition and motivation to deliver even better open-source solutions. Here's the exciting part: we're only a few stars away from reaching the remarkable milestone of 11,000 stars on GitHub!

Join us in creating something extraordinary with Amplication. Click that star button on GitHub and help us soar to new heights. Together, let's make Amplication shine! ✨

Instantly generate
production-ready backend
Never waste time on repetitive coding again.
Try Now

Getting Started

To begin, navigate to Amplication's website at https://app.amplication.com/login and log in using your GitHub account. After login for the first time, you will be directed to the service creation wizard. Follow the steps and complete the wizard to create your service's basic structure, such as naming your service, connecting your GitHub Repository, and selecting a repo style (Monorepo or Polyrepo). You can follow the steps here for more details here https://docs.amplication.com/first-service/.


The next step is to add entities to your database.

In the context of Amplication, an entity is a high-level representation of a data model that maps to a database table in SQL or a collection in NoSQL. Essentially, an entity is a blueprint for a specific type of data object that will be stored in your application's database.

Entities consist of fields, representing the different data pieces that make up an object of that entity type. For example, in the case of the "User1", the fields might include "id," "alias," "message," and so on, depending on the specific requirements of the application.

To do this, navigate to the "Entities" section and click on "Add Entity," giving it a meaningful name. We will add two fields for out use case, "alias" and "message".



You can commit changes to your GitHub repository with your entities set up. Amplication will create a pull request in your repository for you to review and merge. Once the pull request is merged, the backend server is ready to run and you can begin creating the frontend using Angular.

Now, navigate to your GitHub repository and clone it locally.

git clone <git-url>

To get started, navigate to the server director in your freshly cloned repository and run the following commands: You may find the server directory under apps/<servicename-service>

npm i --force
npm run prisma:generate

These commands will install any necessary dependencies and generate the Prisma client. Next, start the PostgreSQL using Docker by typing:

npm run docker:db

Once Docker is running, initialize the database by entering the following:

npm run db:init

Once you have confirmed that everything is set up correctly, you can start the server by running the following:

npm start

Amplication also provides the UI to test your REST API endpoints. This includes a useful built-in functionality that allows you to send requests to your API directly within the page. To use this feature, select any endpoint and click “Try it out”. This will provide you with fields to add headers, and upon execution, you will also receive a curl request that you can run from your terminal or import into Postman to try it out. This feature is helpful when understanding the structure of your response and debugging errors. To test this out, switch to the admin-ui folder and run the Admin UI panel by typing:

npm i --force | npm start

Your Admin UI panel is now up and running at http://localhost:3001. Additionally, you can find a complete list of REST API endpoints at http://localhost:3000/api/.

Creating Angular UI

Let's start by creating an Angular application. Angular comes with the all-powerful CLI, enabling us to seamlessly create applications, workspaces, pipes, and services, reducing development times.

Now within your repository, run the following command to create an Angular Application.

ng new your-app-name

After running the command, you will be prompted to choose various options such as routing and styling format. You can make selections based on your project requirements or simply press Enter to choose the default options. For this application, we will be using SCSS pre-processor and Component Nesting. Next, create the chatroom component using the following command from the CLI

ng g c chatroom

Where g is a short-hand notation for generate and c for component. This same command can also be written as:

ng generate component chatroom

This will generate the chatroom.component.ts where we will write your TypeScript logic, the chatroom.component.html where we will define the basic structure of our chatroom, the chatroom.component.scss where we will style our classes and the chatroom.component.spec.ts where we will write a test to verify our logic.

This also updates the app.module.ts file for us with the required imports while bootstrapping the Angular Application.

Let's start by creating the popup, which asks for a user alias and authenticates each login by generating a unique JSON Web Token. The API to generate the JWT is included within the Amplications endpoints and can be tested via the Admin UI. We will need this JWT to authenticate each of our API calls. This is also what will distinguish what message was sent by which user when n-number of users join our online chatroom, some likely with similar names.

Now, in the chatroom.component.ts itself, we will create a new component for the sake of modularity. We will call this the dialog-popup Component.

Since the template for this component is fairly small, we will add the HTML within the template property itself and will add the same stylesheet path as the one we will use for the chatroom Component.

@Component({
  selector: 'dialog-popup',
  template: `
    <div class="dialog-popup-container">
    <h2 class="dialog-popup-title">Welcome to the Chatroom!</h2>
    <p class="dialog-popup-message">Please enter your alias:</p>
    <input type="text" [(ngModel)]="alias" class="dialog-popup-input" placeholder="Alias">
    <button (click)="saveAlias()" class="dialog-popup-button">Submit</button>
    </div>
  `,
  styleUrls: ['./chatroom.component.scss']
})

Now let's define the DialogPopupComponet class. We will use the OnInit lifecycle hook that is called after Angular has initialized all data-bound properties of a directive since we want to show this popup as soon as the application is bootstrapped. We will inject the HTTP client as dependency Injection as we need to make API Calls to authenticate every user and generate a corresponding JWT Token. We will also use MatDialogRef as a Dependency Injection to create the popup using material UI. Do use Angular Material, and add this dependency using the powerful Angular CLI. Run the following command in the root directory of your project:

ng add @angular/material

You will then be allowed to make certain decisions for your customization such as selecting a pre-built theme or making a custom theme, using Typography styles and Add Aminations.

At the end of this, you’ll see something like this:

Using package manager: npm
✔ Found compatible package version: @angular/material@15.2.9.
✔ Package information loaded.

The package @angular/material@15.2.9 will be installed and executed.
Would you like to proceed? Yes
✔ Packages successfully installed.
? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink        [ Preview:
<https://material.angular.io?theme=indigo-pink> ]
? Set up global Angular Material typography styles? Yes
? Include the Angular animations module? Include and enable animations
UPDATE package.json (1101 bytes)
✔ Packages installed successfully.
UPDATE src/app/app.module.ts (569 bytes)
UPDATE angular.json (2998 bytes)
UPDATE src/index.html (572 bytes)
UPDATE src/styles.scss (181 bytes)

Remember to import the necessary header files from @angular/common and angular/material

import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { HttpClient, HttpHeaders } from '@angular/common/http';

Also, remember to add this component to the declarations array of app.module.ts. And the HttpClientModule and MatDialog Module to the imports array. We have also used the titleCase pipe in this example to beautify the output. Include such pipes in the Providers array though this step is optional.

Your app.module.ts should look something like this:

import { NgModule } from '@angular/core';
import { TitleCasePipe } from '@angular/common'
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ChatroomComponent } from './chatroom/chatroom.component';
import { FormsModule } from '@angular/forms';
import { MatDialogModule } from '@angular/material/dialog';
import { DialogPopupComponent } from './chatroom/chatroom.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    ChatroomComponent,
    DialogPopupComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    MatDialogModule,
    HttpClientModule
  ],
  providers: [TitleCasePipe],
  bootstrap: [AppComponent]
})
export class AppModule { }

Your class should look something like this after the dependency injection:

export class DialogPopupComponent implements OnInit {
  alias: string = '';

  constructor(
    private dialogRef: MatDialogRef<DialogPopupComponent>,
    private http: HttpClient,
    ) { }

Now we need to define the ngOnInit() method. This comes from the OnInt Interface which enables us to handle additional initialization tasks. Here we check if the localStorage has a JWT. In such a case we can close the popup.

ngOnInit() {
    const hasShownPopup = localStorage.getItem('shownPopup');
    if (hasShownPopup) {
      this.dialogRef.close();
    }
  }

Next, we define a function to save the alias the current user enters and generate a JWT corresponding to it. We define the body object of the API call. In our case, a username and password will be needed to generate a JWT. The response to this POST request will contain an accessToken, the JSON Web Token. We store this in the token variable in the localStorage for future use. If the alias already exists or the JWT is successfully done we close the dialog box.

saveAlias() {
    if (this.alias) {
      localStorage.setItem('userAlias', this.alias);
      // localStorage.setItem('shownPopup', 'true');

      const body = {
        username: 'admin',
        password: 'admin'
      };

      this.http.post('<http://localhost:3000/api/login>', body).subscribe(
        (response: any) => {
          // Handle the response here
          const token = response.accessToken;
          // Do something with the token, such as storing it in localStorage
          localStorage.setItem('jwtToken', token);

          // Close the dialog
          this.dialogRef.close(this.alias);
        },
        (error: any) => {
          // Handle any error that occurred during the API call
          console.error('Error:', error);
        }
      );

      this.dialogRef.close(this.alias);
    }
  }

In the end, the dialog-popup component should look like this:

@Component({
  selector: 'dialog-popup',
  template: `
    <div class="dialog-popup-container">
    <h2 class="dialog-popup-title">Welcome to the Chatroom!</h2>
    <p class="dialog-popup-message">Please enter your alias:</p>
    <input type="text" [(ngModel)]="alias" class="dialog-popup-input" placeholder="Alias">
    <button (click)="saveAlias()" class="dialog-popup-button">Submit</button>
    </div>
  `,
  styleUrls: ['./chatroom.component.scss']
})
export class DialogPopupComponent implements OnInit {
  alias: string = '';

  constructor(
    private dialogRef: MatDialogRef<DialogPopupComponent>,
    private http: HttpClient,
    ) { }

  ngOnInit() {
    const hasShownPopup = localStorage.getItem('shownPopup');
    if (hasShownPopup) {
      this.dialogRef.close();
    }
  }

  saveAlias() {
    if (this.alias) {
      localStorage.setItem('userAlias', this.alias);
      // localStorage.setItem('shownPopup', 'true');

      const body = {
        username: 'admin',
        password: 'admin'
      };

      this.http.post('<http://localhost:3000/api/login>', body).subscribe(
        (response: any) => {
          // Handle the response here
          const token = response.accessToken;
          // Do something with the token, such as storing it in localStorage
          localStorage.setItem('jwtToken', token);

          // Close the dialog
          this.dialogRef.close(this.alias);
        },
        (error: any) => {
          // Handle any error that occurred during the API call
          console.error('Error:', error);
        }
      );

      this.dialogRef.close(this.alias);
    }
  }
}

Now that the popup-dialog component is ready let's create the chatroom component and add some styles.

Start by defining some instance variables, alias - of type String to store the alias name of the currently logged-in user, messages - of string[] to store the complete list/array of messages in the chatroom, and a message - of type string to store the current message sent to the backend using the Amplication generated APIs.

We will also use the MatDialog and HttpClient as dependent injection in the constructor of this class:

@Component({
  selector: 'app-chatroom',
  templateUrl: './chatroom.component.html',
  styleUrls: ['./chatroom.component.scss']
})
export class ChatroomComponent implements OnInit {
  alias: string = '';
  messages: string[] = [];
  message: string = '';
  isCurrentUser: boolean = false;

  constructor(
    public dialog: MatDialog,
    public http: HttpClient
    ) { }

Next let's create the openDialog and showPopupBox methods which will primarily function to render a dialog box of our desired size:

showPopupBox() {
    this.openDialog();
  }

  openDialog(): void {
    const dialogRef = this.dialog.open(DialogPopupComponent, {
      width: '250px'
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result) {
        this.alias = result;
        // localStorage.setItem('userAlias', this.alias);
      }
    });
  }

Now, create a method that sends a message to the backend and adds this to the message array. First, getItem - userAlias and JWT from the local storage as we must pass this in the header of our API Call. Ultimately, we push the latest message to the messages array along with the alias name and then reset the message to ‘’.

sendMessage() {
    const userAlias = localStorage.getItem('userAlias');
    const token = localStorage.getItem('jwtToken');

    if (this.message) {
      const body = {
        alias: userAlias,
        message: this.message,
      };

      const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);

      this.http.post('<http://localhost:3000/api/user1s>', body, { headers }).subscribe(
        (response: any) => {
          // set message
          console.log('Response:', response);
        },
        (error: any) => {
          // Handle any error that occurred during the API call
          console.error('Error:', error);
        }
      );

      this.messages.push(`${this.alias}: ${this.message}`);
      this.message = '';
    }
  }

Next, we define the function where we receive the freshly sent message to the backend using Amplication-generated APIs. Here we fetch userAlias and JWT from the localStorage and it in the headers of the GET API call. Now, we filter out and see if there is any new message. We only append to the message array the new messages we found in the previous filter. This helps us prevent rendering the same set of messages multiple times at every state change.

recieveMessage() {
    const userAlias = localStorage.getItem('userAlias');
    const token = localStorage.getItem('jwtToken');

    const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);

    this.http.get('<http://localhost:3000/api/user1s>', { headers }).subscribe(
      (response: any) => {
        // Check if new messages are found
        const newMessages = response.filter((message: any) => {
          return !this.messages.includes(`${message.alias}: ${message.message}`);
        });

        // Add new messages to the messages array
        newMessages.forEach((message: any) => {
          this.messages.push(`${message.alias}: ${message.message}`);
        });

        console.log('Response:', response);
      },
      (error: any) => {
        // Handle any error that occurred during the API call
        console.error('Error:', error);
      }
    );
  }

At last, create the ngOnInit method to call the showPopupBox method as soon as the application is bootstrapped. Reset the messages array and userAlias and JWT for any new user.

We also listen actively, every 1000 milliseconds, for any message change by subscribing to an interval observable and calling the recieveMessage function here.

Remember to import rxjs to use the interval observable.

import { interval } from 'rxjs';

ngOnInit() {
    this.showPopupBox();

    this.messages = [];
    localStorage.removeItem('userAlias');
    localStorage.removeItem('jwtToken');

    interval(1000).subscribe(() => {
      this.recieveMessage();
    });
  }

Now that our chatroom.component.ts is ready, it should look something like this:

import { Component, OnInit } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { interval } from 'rxjs';

@Component({
  selector: 'app-chatroom',
  templateUrl: './chatroom.component.html',
  styleUrls: ['./chatroom.component.scss']
})
export class ChatroomComponent implements OnInit {
  alias: string = '';
  messages: string[] = [];
  message: string = '';

  constructor(
    public dialog: MatDialog,
    public http: HttpClient
    ) { }

  ngOnInit() {
    this.showPopupBox();

    this.messages = [];
    localStorage.removeItem('userAlias');
    localStorage.removeItem('jwtToken');

    interval(1000).subscribe(() => {
      this.recieveMessage();
    });
  }

  showPopupBox() {
    // const userAlias = localStorage.getItem('userAlias');
    // if (userAlias) {
    //   this.alias = userAlias;
    // } else {
    //   this.openDialog();
    // }
    this.openDialog();
  }

  openDialog(): void {
    const dialogRef = this.dialog.open(DialogPopupComponent, {
      width: '250px'
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result) {
        this.alias = result;
        // localStorage.setItem('userAlias', this.alias);
      }
    });
  }

  sendMessage() {
    const userAlias = localStorage.getItem('userAlias');
    const token = localStorage.getItem('jwtToken');

    if (this.message) {
      const body = {
        alias: userAlias,
        message: this.message,
      };

      const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);

      this.http.post('<http://localhost:3000/api/user1s>', body, { headers }).subscribe(
        (response: any) => {
          // set message
          console.log('Response:', response);
        },
        (error: any) => {
          // Handle any error that occurred during the API call
          console.error('Error:', error);
        }
      );

      this.messages.push(`${this.alias}: ${this.message}`);
      this.message = '';
    }
  }

  recieveMessage() {
    const userAlias = localStorage.getItem('userAlias');
    const token = localStorage.getItem('jwtToken');

    const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);

    this.http.get('<http://localhost:3000/api/user1s>', { headers }).subscribe(
      (response: any) => {
        // Check if new messages are found
        const newMessages = response.filter((message: any) => {
          return !this.messages.includes(`${message.alias}: ${message.message}`);
        });

        // Add new messages to the messages array
        newMessages.forEach((message: any) => {
          this.messages.push(`${message.alias}: ${message.message}`);
        });

        console.log('Response:', response);
      },
      (error: any) => {
        // Handle any error that occurred during the API call
        console.error('Error:', error);
      }
    );
  }
}

@Component({
  selector: 'dialog-popup',
  template: `
    <div class="dialog-popup-container">
    <h2 class="dialog-popup-title">Welcome to the Chatroom!</h2>
    <p class="dialog-popup-message">Please enter your alias:</p>
    <input type="text" [(ngModel)]="alias" class="dialog-popup-input" placeholder="Alias">
    <button (click)="saveAlias()" class="dialog-popup-button">Submit</button>
    </div>
  `,
  styleUrls: ['./chatroom.component.scss']
})
export class DialogPopupComponent implements OnInit {
  alias: string = '';

  constructor(
    private dialogRef: MatDialogRef<DialogPopupComponent>,
    private http: HttpClient,
    ) { }

  ngOnInit() {
    const hasShownPopup = localStorage.getItem('shownPopup');
    if (hasShownPopup) {
      this.dialogRef.close();
    }
  }

  saveAlias() {
    if (this.alias) {
      localStorage.setItem('userAlias', this.alias);
      // localStorage.setItem('shownPopup', 'true');

      const body = {
        username: 'admin',
        password: 'admin'
      };

      this.http.post('<http://localhost:3000/api/login>', body).subscribe(
        (response: any) => {
          // Handle the response here
          const token = response.accessToken;
          // Do something with the token, such as storing it in localStorage
          localStorage.setItem('jwtToken', token);

          // Close the dialog
          this.dialogRef.close(this.alias);
        },
        (error: any) => {
          // Handle any error that occurred during the API call
          console.error('Error:', error);
        }
      );

      this.dialogRef.close(this.alias);
    }
  }
}

Now head over to the chatroom.component.html to add this:

<div class="chatroom-container">
    <div class="messages-container">
      <div class="message" *ngFor="let msg of messages">{{ msg | titlecase }}</div>
    </div>
    <div class="input-container">
      <input type="text" [(ngModel)]="message" placeholder="Type a message...">
      <button (click)="sendMessage()">Send</button>
    </div>
  </div>

Here we are using Angular’s two-way data binding to update the message from the chatbox that the user enters.

Now head over to chatroom.component.scss to add some style to the chatroom:

/* chatroom.component.scss */

.chatroom-container {
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  .messages-container {
    flex: 1;
    overflow-y: auto;
    padding: 10px;
  }

  .message {
    background-color: #f2f2f2;
    padding: 8px;
    margin-bottom: 5px;
    border-radius: 4px;
  }

  .input-container {
    position: fixed;
    top: 90%;
    width: 90%;
    display: flex;
    align-items: center;
    margin-left: 8vb;
    padding: 10px;
    background-color: #f2f2f2;
  }

  .input-container input {
    flex: 1;
    margin-right: 10px;
    padding: 8px;
    border: none;
    border-radius: 4px;
  }

  .input-container button {
    padding: 8px 12px;
    background-color: #4caf50;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  /* dialog-popup.component.scss */

  .dialog-popup-container {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
  }

  .dialog-popup-title {
    font-size: 24px;
    margin-bottom: 10px;
  }

  .dialog-popup-message {
    font-size: 16px;
    margin-bottom: 10px;
  }

  .dialog-popup-input {
    width: 100%;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  .dialog-popup-button {
    padding: 8px 12px;
    margin: 8px 0;
    background-color: #4caf50;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  .user-bubble {
    background-color: #4caf50;
    padding: 8px;
    margin-bottom: 5px;
    border-radius: 4px;
  }

  .other-bubble {
    background-color: #f2f2f2;
  }

Now head over to app.module.html and replace the boilerplate with the following component nesting using the selector of the chatroom Component.

<app-chatroom></app-chatroom>

Now we are ready to test our freshly created chatroom. To do this ensure your messaging service is running. Now let's run two instances of the angular application on two different ports on two different terminals using the following command:

ng serve

When asked in the second terminal if you'd like to use a different port since Angular's default port 4200 is busy. Select yes.


Once the Angular Live Development Server listens on the respective ports, you will see a prompt to enter your alias name.


Enter your Alias name and click on submit. Both sessions will generate their own unique JWT Token and store it in the local storage.


Any message sent on one console will be actively receipted by the other thus creating a real-time online chatroom using Angular and Amplication.


Thanks to our Community

We hope you found this article on creating a chatroom in Angular using Amplication informative and inspiring. It is our pleasure to bring you valuable insights and practical guidance. We would like to extend our gratitude to Akshita Dixit and Hamadaan Ali, dedicated members of the Amplication open source community for their invaluable contribution to this tutorial.

We invite all developers and enthusiasts to join us in this collaborative journey. Your contributions can help shape the future of Amplication and empower developers worldwide. If you have any questions, encountered challenges, or simply want to connect with fellow developers, we encourage you to join the Amplication Discord server. There, you can engage in meaningful discussions, seek guidance, and share your experiences with the community.