Table of Contents


How to Integrate Google Gemini AI into Your Angular App [Step-by-Step Guide]

Introduction

Integrating AI-powered features into modern web applications has become important to deliver smarter, more responsive user experiences. This project demonstrates how to integrate Google Gemini AI with an Angular 18 application, containing features such as dynamic chat conversations, text and contextually aware responses. Gemini is a multimodal AI model that generates text, as well as other types of information like code.

Prerequisites

Node.js v20.19.2

Angular cli v18.2.20

Before we begin, ensure Node.js and Angular cli is installed.

Step 1: Create a new angular application

Run the following command to generate a new Angular app:


ng new ai-powered-app
cd ai-powered-app						
						

Note: During setup, choose the default configuration (no routing, CSS for styling, unless otherwise needed).

After creation serve the project using command


npm start or ng serve						
						

Step 2: Remove the Default App Content

By default, Angular includes sample HTML in the AppComponent. To prepare for a routed layout:

  1. Open app.component.ts.
  2. Replace the existing template code with the following:

<router-outlet></router-outlet>
					

This enables Angular’s router to display components based on the current route.

Step 3: Generate Project Components

Use Angular CLI commands to scaffold the necessary components. Open a terminal in your project directory and run the following:


Run ‘ng g c components/layout’
Run ‘ng g c components/sidebar’
Run ‘ng g c components/bubble’						
						

These components will be used to structure your application page and layout.

LayoutComponent is created as a parent component for chats and will contain BubbleComponent and SidebarComponent as child component.

Step 4: Configure Application Routing

Update your app.routes.ts file to define application routes. This includes a default redirect and a route for the layout component.


import { Routes } from '@angular/router';
import { LayoutComponent } from './components/layout/layout.component';

export const routes: Routes = [
    {path:'', redirectTo:'q', pathMatch:'full'},
    {path:'q', component:LayoutComponent}
];						
						

Step 5: Add Material UI and Bootstrap

Angular Material UI is added to enhance UI components in chat bot. In the process run the command to install angular material. This will find the compatible version of Angular Material for Angular v18.2.20


Run ‘ng add @angular/material’						
						

Next, choose any of the themes for example the Azure/Blue theme, also include animations and typography.

After this restart the app once to update angular.json and to add Bootstrap v5.3 using cdn links add these tags to attach css and javascript bundle files in index.html.


<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AiPoweredApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
</head>
<body class="mat-typography">
  <app-root></app-root>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO" crossorigin="anonymous"></script>
</body>
</html>
					

Step 6: Creating the design layout for Chat Bot

Now let’s start working on the layout design of the chatbot first. On the left side the sidebar will be created and the chat section on the right side.

A component selector of the sidebar component is added in the layout component, shown with a condition of opening of sidebar depending on the innerwidth of the window as it will be hidden if inner width is less than 992px. Here 992px will be the breakpoint to create a responsive screen. That's why we have added styles for left and right sections dynamically based on screen width.

Also, Event emitters are used to open and close the sidebar which is called from the sidebar component as shown below. The flag for showing sidebar is passed to sidebar.component.

The list section in the sidebar component is created for a list of history of chat conversations and a bottom section for new chat features and name of user.

Additions in layout.component.html


<div class="parent container-fluid px-0">
    <div class="sidebar shadow" [ngStyle]="leftStyle()">
        @if(isSideBarOpened){
        <app-sidebar [isSideBarOpened]="isSideBarOpened" (closeSidebar)="toggleSidebar()"></app-sidebar>
        }
    </div>
    <div class="chat-section py-3" [ngStyle]="rightStyle()">
    </div>
</div>							
							

Additions in layout.component.css


.parent{
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    position: relative;
}
.sidebar{
    height: 100vh;
    transition:all 0.3s;
    background-color: white;
    position:relative;
    left: 0;
}
.chat-section{
   position: relative;
   z-index: 0;
}			
							

Additions in layout.component.ts

Following functions are added in class LayoutComponent and add NgStyle and class SidebarComponent to imports array. Also, import HostListener to check the resizing of the window. Also, added a check for window width in ngOnInit to show the sidebar.

The toggleSidebar function is created to open and close the sidebar.

The leftStyle function is created for styling of sidebar in case of screen width less than or more than 992px and is sidebar open or not.

The rightStyle function is created for styling of the chat section in case of screen width less than or more than 992px and is sidebar open or not.


  isSideBarOpened: boolean = true;
  ngOnInit(): void {
    this.checkSideBar()
  }
  toggleSidebar() {
    this.isSideBarOpened = !this.isSideBarOpened
  }
  leftStyle() {
    let width = window.innerWidth < 992 ? (this.isSideBarOpened ? 50 : 0): (this.isSideBarOpened ? 20 : 0)
    let padding = this.isSideBarOpened ? 1 : 0
    let zIndex = window.innerWidth < 992 ? 2: 0
    return {'width': `${width}%`, 'padding':`${padding}rem`, 'z-index':`${zIndex}`}
  }
  rightStyle() {
    let width = window.innerWidth < 992 ? '100%' : (this.isSideBarOpened ? '80%' : '100%')
    let left = window.innerWidth < 992 ? '0' : 'unset'
    return {width, left}
  }
  @HostListener('window:resize', ['$event'])
  onResize(event: Event) {
    this.checkSideBar()
  }
  checkSideBar() {
    this.isSideBarOpened = window.innerWidth < 992 ? false : true
  }
							

Additions in sidebar.component.html

Here in the sidebar template file, three sections of header, list section and sidebar bottom section is created.

In the header, a close button is provided for closing the sidebar.

In the list section, a design for the list of conversations of chat history is created.

In the bottom section, the name of the user and a new chat button is created. Styling of this section added based on screen width.


<div class="container-fluid px-0 position-relative">
  <div class="title d-flex justify-content-between mt-1">
    <p class="fs-5 my-2 fw-bold">Conversation</p>
    <button mat-mini-fab aria-label="Close Sidebar" class="rounded-circle" (click)="closeSidebar.emit()">
      <mat-icon>logout</mat-icon>
    </button>
  </div>
  <div class="list mt-5">
    <mat-list>
      <mat-list-item
        class="border border-secondary-subtle bg-light"
        class="rounded-5 cursor-pointer">
        <div matListItemTitle class="cursor-pointer list-item " >Chat Conversation History</div>
      </mat-list-item>
    </mat-list>
  </div>
  <div class="sidebar-bottom mb-4" [ngStyle]="getSidebarBottomStyle()">
    <mat-list class="mt-3">
      <mat-list-item class="rounded-5 cursor-pointer border border-secondary-subtle bg-light">
        <mat-icon matListItemIcon>edit</mat-icon>
        <div matListItemTitle class="cursor-pointer list-item ">New Chat</div>
      </mat-list-item>
    </mat-list>
    <div class="p-1 rounded-pill cursor-pointer border border-secondary-subtle bg-light d-flex">
      <button mat-mini-fab aria-label="Close Sidebar" class="rounded-circle shadow-none me-2"><mat-icon>person</mat-icon>
      </button>
      <div class="cursor-pointer list-item my-auto">John Parkson</div>
    </div>
  </div>
</div>
							

Additions in sidebar.component.css


.list{
    bottom: 150px;
    overflow-y: auto;
    overflow-x: hidden;
    max-height: 400px;
}
.list-item{
    font-size: 0.8rem!important;
}
.sidebar-bottom{
    position: fixed;
    bottom: 0;
}							
							

Additions in sidebar.component.ts

Add MatIconModule, MatButtonModule, MatListModule, TitleCasePipe, NgStyle in imports array.

The closeSidebar event is created to close the sidebar using the closing button.


  @Input() isSideBarOpened: boolean = false
  @Output() closeSidebar = new EventEmitter<void>()
  getSidebarBottomStyle() {
    let width = window.innerWidth < 992 ? 50 : 20
    return { 'width': `calc(${width}% - 2rem)` }
  }							
							

The update in design looks like this.

Step 7: Add Global Styling

Add these classes in styles.css to use in multiple components.

Class bg-blue and color-blue are created for button styling.


html, body { height: 100%; overflow: hidden;}
body { margin: 0; font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif }
.cursor-pointer{
    cursor: pointer;
}
.bg-blue{
    background-color: #e0e0ff !important;
}
.color-blue{
    color: rgb(0, 0, 110) !important;
}
@media screen and (max-width:415px) {
    .fs-5{
        font-size: 1rem !important;
    }
}						
						

Step 8: Design of Header, Chats and Prompt

After creating the sidebar, let's focus on the chats section on the right side in layout.component.ts file.

Additions in layout.component.html

Add these divs in the chat-section.

First is the header section, containing a button for opening the sidebar and search bar for searching the chat messages.

Next in the chat section, the component selector of bubble component is added to pass the chat message and a spinner is added to show in waiting for response from gemini.

At Last, a prompt section is added to write text or upload images in a prompt to send to gemini.


<div class="chat-section py-3" [ngStyle]="rightStyle()">
        <div class="header container-fluid py-3" [ngStyle]="rightStyle()">
            <div class="row">
                <div class="col-6 d-flex">
                    @if(!isSideBarOpened){
                    <button mat-mini-fab aria-label="Open Sidebar" class="me-4 rounded-circle"
                        (click)="toggleSidebar()">
                        <mat-icon>menu</mat-icon>
                    </button>
                    }
                    <p class="fs-5 my-auto fw-bolder">Gemini_Angular</p>
                </div>
                <!-- <div class="col-sm-4"></div> -->
                <div class="col-6 d-flex justify-content-end">
                    <div class="p-1 rounded-pill cursor-pointer border border-secondary-subtle bg-light d-flex searchbar" [ngStyle]="getSearchBarStyle()">
                        <div class="cursor-pointer list-item my-auto w-100">
                            <input type="text" class="form-control border-0 bg-transparent" placeholder="Search..." >
                        </div>
                        <button mat-mini-fab aria-label="User" class="ms-4 rounded-circle">
                            <mat-icon>search</mat-icon>
                        </button>
                    </div>
                </div>
            </div>
        </div>

        <div class="chat ps-3 w-100" id="chatMessage">
            <div class="row">
                <div class="col-md-1 col-xl-2"></div>
                <div class="col-md-10 col-xl-8">
                    <app-bubble></app-bubble>
                    @if(isLoadingShown){
                    <div class="d-flex">
                        <button mat-mini-fab aria-label="Open Sidebar" class="me-2 rounded-circle shadow-none">
                            <mat-icon>auto_awesome</mat-icon>
                        </button>
                        <mat-card appearance="outlined" class="my-auto">
                            <mat-card-content class="py-0 my-3 spinner_parent">
                                <div class="spinner_dot"></div>
                                <div class="spinner_dot"></div>
                                <div class="spinner_dot"></div>
                            </mat-card-content>
                        </mat-card>
                    </div>
                    }
                </div>
                <div class="col-md-1 col-xl-2"></div>
            </div>
        </div>

        <div class="prompt container-fluid" [ngStyle]="rightStyle()">
            <div class="row">
                <div class="col-md-1 col-xl-2"></div>
                <div class="col-md-10 col-xl-8 prompt-parent">
                    <div class="input-group input-group-lg mb-4 ">
                        <input type="text" class="form-control" placeholder="Ask any question...">
                        <button class="btn btn-outline-secondary img-button" type="button" id="button-addon2">
                            <label for="fileInput">
                                <mat-icon class="cursor-pointer fs-4">add_photo_alternate</mat-icon>
                                <input type="file" id="fileInput" class="d-none">
                            </label>
                        </button>
                    </div>
                    <button mat-mini-fab aria-label="Send Prompt" class="rounded-circle mt-1">
                        <mat-icon>send</mat-icon>
                    </button>
                </div>
                <div class="col-md-1 col-xl-2"></div>
            </div>
        </div>
    </div>
							
							

Additions in layout.component.css

This is the styling for header, prompt and chat section.


.header{
    position: fixed;
    top: 0;
    z-index: 1;
    box-shadow: 0 5px 10px rgb(220, 220, 220);
    max-width: 100%;
}
.prompt{
    position: fixed;
    bottom: 0;
    z-index: 1;
    max-width: 100%;
}
.chat{
    position: absolute;
    top: 100px;
    bottom: 90px;
    overflow-y: auto;
    overflow-x: hidden;
    scroll-behavior: smooth;
}
.prompt-parent{
    padding-left: 60px;
    display: flex;
    gap: 10px;
}
.spinner_dot{
    background-color: rgb(0, 0, 110);
    height: 10px;
    width: 10px;
    border-radius: 50%;
    animation-name:blink;
    animation-duration: 1s;
    animation-iteration-count: infinite;
}
@keyframes blink{
    from{
        transform: scale(0);
    }
    to{
        transform: scale(1);
    }
}
.spinner_parent{
    display: flex;
    gap: 3px;
}
.prompt .form-control, .img-button{
    padding: .5rem 1rem;
    font-size: 1rem;
    border-radius: var(--bs-border-radius-pill);
    background-color: #faf9fd;
    border-color: #c4c8cb;
}
.prompt .form-control{
    border-right-width: 0;
}
.prompt .form-control:focus{
    box-shadow: none;
}
.img-button{
    border-left-width: 0;
}
.img-button:active{
    background-color: #faf9fd;
    border-color: #c4c8cb;
}
.searchbar input:focus  {
    box-shadow: none;
}							
							

Additions in layout.component.ts

Add MatIconModule, MatCardModule, BubbleComponent, MatButtonModule in imports array and a function to provide search bar width according to screen width.


  isLoadingShown:boolean = true
  getSearchBarStyle() {
     let width = window.innerWidth < 992 ? "100%" : "50%"
    return { width }
  }							
							

Step 9: Design of Chat Bubble

Let’s design the bubble for chat messages in Bubble Component

Additions in bubble.component.html

An Icon and a card for chat messages is added.


<div class="w-100 d-flex mb-4 d-flex align-items-start">
	<button mat-mini-fab aria-label="Open Sidebar" class="me-2 rounded-circle shadow-none">
		<mat-icon>person</mat-icon>
	</button>
	<mat-card appearance="outlined" class="w-100 overflow-auto">
		<mat-card-content class="py-0 my-2">Chat Messages</mat-card-content>
		<img mat-card-image src="" class="w-100" alt="">
	</mat-card>
</div>						
							

Additions in bubble.component.ts

Add MatCardModule, MatIconModule, MatButtonModule in imports array. The Updated design looks like

Step 10: Installation and Setup

To integrate gemini into the angular app run this command to install @google/genai


Run ‘npm install @google/genai’						
						

Create environments using command


Run ‘ng g environments’				
						

Now add googleAiApiKey in the environments file. To get this googleAiApiKey create a google account and open https://aistudio.google.com/apikey this link and create API key.

Add the API key into environment.development.ts


export const environment = {
    API_KEY:'***************************************'
};							
							

Also add in API key into environment.ts


export const environment = {
    API_KEY:'***************************************',
    production:false
};
							

Step 11: Get Response from gemini

Now, to send the prompt message and get a response from gemini, first set up a generate-result service to create functions for sending prompt and receiving responses.

Create the following functions in in generate-result.service.ts

The sendQuestion is created to send a prompt to gemini and will get a response using result$.


import { Injectable } from '@angular/core';
import { GoogleGenAI   } from '@google/genai';
import { environment } from '../../environments/environment.development';
import { BehaviorSubject, from, shareReplay, switchMap } from 'rxjs';
@Injectable({
  providedIn: 'root'
})
export class GenerateResultService {

  private generateResult: GoogleGenAI;
  private q$ = new BehaviorSubject<{ ques: string, image?: string } | null>(null);

  result$ = this.q$.pipe(
    switchMap(req => {
      if (!req) return [];
      const contents: any[] = [
        { role: 'user', parts: [{ text: req.ques }] }
      ];
      if (req.image) {
        contents.push({
          role: 'user',
          parts: [
            {
              inlineData: {
                mimeType: 'image/jpeg',
                data: req.image.split(',')[1],
              }
            }
          ]
        });
      }
      return from(this.generateResult.models.generateContent({
        model: 'gemini-2.0-flash-001',
        contents
      }));
    }),
    shareReplay(1)
  );
  constructor() {
    this.generateResult = new GoogleGenAI({ apiKey: environment.API_KEY })
  }
  sendQuestion(ques: string, image?: string) {
    this.q$.next({ ques, image });
  }
}						
						

Create an interface Message used to store chat messages in an array by running this command.


Run `ng g interface interfaces/message`						
						

The message key to store the response, sender key to store whether the sender is user or chatBot


export interface Message {
    message:any,
    sender:'user' | 'chatBot',
    image:string
}						
						

Modifications in layout.component.ts

Create an instance of this service, messages array to store chat messages, a variable for prompt question, file and filename to store file data url and file’s name.

On clicking the image icon, the onFileSelection function is executed, the file is uploaded and read as a data url. The send button is only enabled if a prompt is written.

The sendPrompt function is called to send the prompt to gemini and get the response in the constructor. Adding the prompt and response in messages array. And Scrolling to bottom after getting the response.


  messages: Message[] = []
  promptQuestion: any = ""
  filename: any = "Choose Image"
  file: any
  constructor(private _generateS: GenerateResultService) {
    _generateS.result$.subscribe(res => {
      let text: any = res?.candidates?.[0]?.content?.parts?.[0]?.text ?? '[No response]';
      this.pushToMessages(text, 'chatBot', "")
      this.isLoadingShown = false
      setTimeout(() => {
        this.scrollToBottom()
      }, 500)
    });
  }
  sendPrompt() {
    if (!!this.promptQuestion.trim() || !!this.file) {
      this._generateS.sendQuestion(this.promptQuestion, this.file)
      if (!this.promptQuestion) {
        this.promptQuestion = "Image"
      }
      this.pushToMessages(this.promptQuestion, 'user', this.file)
      this.isLoadingShown = true
      setTimeout(() => {
        this.scrollToBottom()
      }, 500)
      this.promptQuestion = ""
      this.file = undefined
      this.filename = "Choose Image"
    }
  }
  pushToMessages(message: any, sender: any, file: any) {
    this.messages.push({ message: message, sender: sender, image: file })
  }
  scrollToBottom() {
    let ele: any = document.getElementById('chatMessage')
    ele.scrollTop = ele.scrollHeight
  }
  onFileSelection(e: any) {
    this.filename = "Choose Image"
    this.file = undefined
    const file = e.target.files[0]
    const fileReader = new FileReader();
    fileReader.onload = () => {
      this.file = fileReader.result as string;
    };
    if(!!file){
      fileReader.readAsDataURL(file);
      this.filename = file.name
    }
  }
  getImageBtnColor() {
    let color = this.filename == 'Choose Image' ? '#44474e' : 'rgb(0, 0, 110)'
    return { color }
  }
  isDisableSendButton() {
    return (!!this.promptQuestion || !!this.file) ? (this.isLoadingShown ? true : false) : true
  }
							
							

Import FormsModule in imports array.

Modifications in layout.component.html

This is updated in the prompt section in layout.

Add the variable promptQuestion to the input box using ngModel and call function sendPrompt to submit the prompt. The onFileSelection function added on change of file input.


<div class="col-md-10 col-xl-8 prompt-parent">
     <div class="input-group input-group-lg mb-4 ">
          <input type="text" class="form-control" placeholder="Ask any question..." [(ngModel)]="promptQuestion" id="prompt" (keydown.enter)="sendPrompt()">
          <button class="btn btn-outline-secondary img-button" type="button" id="button-addon2" [ngStyle]="getImageBtnColor()" [title]="filename">
               <label for="fileInput">
                    <mat-icon class="cursor-pointer fs-4">add_photo_alternate</mat-icon>
                    <input type="file" id="fileInput" (change)="onFileSelection($event)" class="d-none">
               </label>
          </button>
     </div>
     <button mat-mini-fab aria-label="Send Prompt" class="rounded-circle mt-1" (click)="sendPrompt()" [disabled]="isDisableSendButton()">
          <mat-icon>send</mat-icon>
     </button>
</div>
							

Step 12: Showing Chat Messages

To show the prompt and response in chat, let’s add the bubble component selector in for loop on messages array, showing the message sender icons depending upon the message sender in the array. Also, making the spinner work when we are waiting for a response from Gemini.


 isLoadingShown:boolean = false;						
						

Modifications in layout.component.html

This is updated in the chat section in layout.


<div class="col-md-10 col-xl-8">
     @for(i of messages; track i; let index = $index)
          {<app-bubble [i]="i" [id]="'id_'+index" ></app-bubble>}
     @if(isLoadingShown){
          <div class="d-flex">
               <button mat-mini-fab aria-label="Open Sidebar" class="me-2 rounded-circle shadow-none
                    <mat-icon>auto_awesome</mat-icon>
               </button>
               <mat-card appearance="outlined" class="my-auto">
                    <mat-card-content class="py-0 my-3 spinner_parent">
                         <div class="spinner_dot"></div>
                         <div class="spinner_dot"></div>
                         <div class="spinner_dot"></div>
                    </mat-card-content>
               </mat-card>
          </div>
     }
</div>							
							

To get the response from gemini parsed run the following command to install maked package

Modifications in bubble.component.ts

This is to be added in ts file of bubble component and add NgClass to the imports array.

Variable i is passed from layout component to bubble containing the detail of chat message to be shown in bubble.

Using marked from package Marked the response message is parsed to show text with proper spacing and styling as in parse function.

The getIcon function used to show the icon depending upon the sender.


 @Input() i:any
  updatedMessage: SafeHtml = '';
  constructor(private sanitizer: DomSanitizer) {}
  ngOnInit(){
    this.parse()
  }
  async parse(){
    if(this.i?.sender == 'chatBot'){
    let parsedText = await marked.parse(this.i?.message);
    this.updatedMessage = this.sanitizer.bypassSecurityTrustHtml(parsedText);
    }
    else{
      this.updatedMessage = this.i?.message
    }
  }
  getIcon(){
    if(this.i?.sender == 'user'){
      return 'person'
    }
    else if(this.i?.sender == 'chatBot'){
      return 'auto_awesome'
    }
    else{
      return ''
    }
  }							
							

Modifications in bubble.component.html

The updatedMessage is provided in the innerHTML of the card.


<div class="w-100 d-flex mb-4 d-flex align-items-start">
    <button mat-mini-fab aria-label="Open Sidebar" class="me-2 rounded-circle shadow-none">
        <mat-icon>{{getIcon()}}</mat-icon>
    </button>
    <mat-card appearance="outlined" class="w-100 overflow-auto">
        <mat-card-content [innerHTML]="updatedMessage" class="py-0 my-2"></mat-card-content>
        <img mat-card-image [src]="i?.image" class="w-100" alt="" [ngClass]="!!i?.image ? ' border border-2': ''">
    </mat-card>
</div>							
							

Modifications in bubble.component.css

This is styling added for response from gemini to be shown in chat message.


.mat-card-content {
  white-space: pre-line;
  line-height: 1.5;
}
.mat-card-content code {
  background: #f0f0f0;
  padding: 2px 4px;
  font-family: monospace;
  border-radius: 4px;
}
.mat-card-content a {
  color: #3888e3;
  text-decoration: underline;
}							
							

Step 13: New chat feature and chat history

To create new chat feature and chat history, add a chat index in generate-result.service.ts


chatIndex:number = -1						
						

Modifications in layout.component.ts

Add a history array to store history of chat conversations and to be stored in local storage. Get the previous history stored in localstorage in ngOnInit.

Update pushToMessage function to get the history array updated on every prompt and response and update the index of chat history on which user is working. This will be used by the openChat function to open a particular chat.

The createNewChat function assigns an empty array to messages to store new chat and changes the chatIndex to -1. The createNewChat function and openChat function will be called from the sidebar function using the event emitter.


  history: any[][] = []
  ngOnInit(): void {
    if (!!localStorage.getItem('history')) {
      this.history = JSON.parse(localStorage.getItem('history') ?? '')
    }
    this.checkSideBar()
  }
  pushToMessages(message: any, sender: any, file: any) {
    this.messages.push({ message: message, sender: sender, image: file })
    let messagesLength = this.messages.length
    if (messagesLength == 1) {
      this.history.unshift(this.messages)
      this._generateS.chatIndex = 0
    }
    else {
      this.history.splice(this._generateS.chatIndex, 1, this.messages)
    }
    localStorage.setItem("history",JSON.stringify(this.history))
  }
  createNewChat() {
    this.messages = []
    this.message = {}
    document.getElementById('prompt')?.focus()
    this._generateS.chatIndex = -1
  }
  openChat(chat: any) {
    this.messages = chat
    setTimeout(() => {
      this.scrollToBottom()
    }, 500)
  }							
							

Modifications in layout.component.html

The history array is passed and openChat and createNewChat events are triggered.


<div class="sidebar shadow" [ngStyle]="leftStyle()">
        @if(isSideBarOpened){
        <app-sidebar [isSideBarOpened]="isSideBarOpened" [history]="history" (closeSidebar)="toggleSidebar()"
            (createNewChat)="createNewChat()" (openChat)="openChat($event)"></app-sidebar>
        }
</div>						
						

Modifications in sidebar.component.html

These are the two sections in sidebar component, list and sidebar-bottom.

For loop is added to create a list of history items and highlighted if any of the chats is selected using getHighlighted function.

The chooseChat function is called to open a particular chat. And the createNewChat function is called to create a new chat.


 <div class="list mt-5">
    <mat-list>
      @for (item of history; track item; let index = $index) {
      <mat-list-item
        [ngClass]="getHighlighted(index) ? 'border border-secondary-subtle bg-light' : 'border border-white bg-white' "
        class="rounded-5 cursor-pointer">
        <div matListItemTitle class="cursor-pointer list-item " (click)="chooseChat(item, index)">{{item?.[0]?.message | titlecase}}</div>
      </mat-list-item>
      }
    </mat-list>
  </div>
  <div class="sidebar-bottom mb-4" [ngStyle]="getSidebarBottomStyle()">
    <mat-list class="mt-3">
      <mat-list-item class="rounded-5 cursor-pointer border border-secondary-subtle bg-light">
        <mat-icon matListItemIcon>edit</mat-icon>
        <div matListItemTitle class="cursor-pointer list-item " (click)="createNewChat.emit()">New Chat</div>
      </mat-list-item>
    </mat-list>
    <div class="p-1 rounded-pill cursor-pointer border border-secondary-subtle bg-light d-flex">
      <button mat-mini-fab aria-label="Close Sidebar" class="rounded-circle shadow-none me-2"><mat-icon>person</mat-icon>
      </button>
      <div class="cursor-pointer list-item my-auto">John Parkson</div>
    </div>
  </div>							
							

Modifications in sidebar.component.ts

Add NgClass to the imports array.


  @Input() history: any[][] = []
  @Output() createNewChat = new EventEmitter()
  @Output() openChat = new EventEmitter()
  constructor(private _generateS: GenerateResultService) { }
  chooseChat(item: any, index: any) {
    this._generateS.chatIndex = index
    this.openChat.emit(item)
  }
  getHighlighted(index: any) {
    if (index === this._generateS.chatIndex) {
      return true
    }
    else {
      return false
    }
  }							
							

Step 14: Search Functionality

Search functionality is used to search any of the chat conversations.

Modifications in layout.component.html

To create search functionality do the following changes in the header section, add a searchText variable in the input box and function search to search button.


<div class="col-6 d-flex justify-content-end">
     <div class="p-1 rounded-pill cursor-pointer border border-secondary-subtle bg-light d-flex searchbar" [ngStyle]="getSearchBarStyle()">
          <div class="cursor-pointer list-item my-auto w-100">
               <input type="text" class="form-control border-0 bg-transparent" placeholder="Search..." [(ngModel)]="searchText" (keydown.enter)="search()">
          </div>
          <button mat-mini-fab aria-label="User" class="ms-4 rounded-circle" (click)="search()" [disabled]="searchText==''">
               <mat-icon>search</mat-icon>
          </button>
     </div>
</div>							
							

Modifications in layout.component.ts

Create the functions search which searches the text from the array and scrolls to that particular message.


  searchText: any = ''
  search() {
    if (this.messages.length > 0) {
      let filteredArray = this.messages.filter((x: any, i) => {
        x['index'] = i
        return x.message.toLowerCase().includes(this.searchText.trim())
      })
      if (filteredArray.length > 0) {
        let obj: any = filteredArray[0]
        let ele: any = document.getElementById('id_' + obj.index)
        if (!!ele) {
          ele.scrollIntoView({ behavior: 'smooth', block: 'start' })
        }
      }
    }
    else {
      this.searchText = ""
    }
  }							
							

The final design after functionality implementation looks like:

Conclusion :

Integrating Google Gemini AI into and using angular’s 18 architecture, adding RxJS patterns like switchMap and shareReplay, ensures data handling, while Angular Material and Bootstrap provides better UX and responsive UI. Using best ways possible in functionality and design, AI-Powered apps are created for upcoming innovations.

Ready to Build Something Amazing?

Get in touch with Prishusoft – your trusted partner for custom software development. Whether you need a powerful web application or a sleek mobile app, our expert team is here to turn your ideas into reality.

image