Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Chatbot in Angular With DialogFlow API.ai

Episode 58 written by Jeff Delaney
full courses and content on fireship.io


In this lesson, we are going to build a chatbot in Angular from scratch using the Dialogflow conversation platform (formerly known as API.ai). Natural language processing (NLP) is one of the most challenging problems in machine learning. Over the past couple years, advances in large scale deep neural networks have made NLP technology available to the average developer.

In addition to Angular, you can deploy your bot to a wide variety of platforms with a single click - including Slack, Facebook Messenger, and many others.

This project is open source and can be found on github - Angular Chatbot.

angular chatbot demo with DialogFlow

Initial Setup Guide

Follow the steps below to get up and running quickly.

New Angular App from Scratch

For this tutorial, I will be starting a brand new Angular app from scratch. Make sure you are using v4.2 or later to take advantage of new Angular Animation features.

npm install -g @angular/cli
ng new chatbot
cd chatbot

Install Required Libraries

Our app has a only one extra dependency - the DialogFlow JavaScript SDK. It is written in TypeScript, so we can install it to the dev dependencies.

Currently, the SDK is still named api-ai, but I imagine this will change to dialogflow in the future. You can find the latest official SDKs here.

npm install api-ai-javascript --save-dev

Fleshing out a Feature NgModule

We’re going to put all of our code into a feature module. This is a good practice in Angular to keep your code isolated and maintainable.

ng g module chat
ng g service chat -m chat
ng g component chat/chat-dialog -m chat

The module should look like this. We only need to add the Angular FormsModule to the imports and add the ChatDialogComponent to exports.

/// chat.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ChatService } from '../chat.service';
import { ChatDialogComponent } from './chat-dialog/chat-dialog.component';


@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
ChatDialogComponent
],
exports: [ ChatDialogComponent ], // <-- export here
providers: [ChatService]
})
export class ChatModule { }

Then import the chat module into the app module

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

import { AppComponent } from './app.component';

import { ChatModule } from './chat/chat.module';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ChatModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Now we have an isolated feature module that can be used in the main app.

You can also do this by loading the component with the Angular Router, but our app is not using routing for this example.

<!-- app.component -->
<chat-dialog></chat-dialog>

initial chatbot angular app feature module

Building a Basic DialogFlow Agent

Now that we have a basic Angular app, let’s configure our first chatbot Agent in Dialogflow.

Create an Agent

An Agent is essentially a container for a chatbot. You should try give them tasks that they can solve easily - set them up for success. In this example, I am giving my agent a name of AngularBot.

Create an Intent

An intent is something your user is trying to accomplish. Here’s how the cycle works.

  1. User invokes an intent by ask “How do I do XZY”?
  2. Bot recognizes question
  3. Attempts to fulfill the intent by asking more questions or doing something on the backend.

Now let’s create a simple intent.

first chatbot intent in dialogflow

  1. Add a user expression “What is a component?” (This is a common question you would expect a user to ask, add multiple variants to optimize the algorithm.)
  2. Add a text response “It’s just JavaScript” (This is what the bot will say when it recognizes this user question)
  3. Test it out. Try typing “Hey dude what’s a component?” (Magically, the bot recognizes this a the question and responds accordingly)

Creating Small Talk

I’d like to point out that you can easily program your bot for common chit-chat style questions in the Small Talk panel. This will give your bot a Siri or Alexa-like level of interactivity. Program it to give funny or intelligent responses that impress your audience.

first chatbot intent in dialogflow

Using the Chatbot Agent in Angular

Now that we have a basic agent, let’s see how we might allow a user to send an receive messages in the Angular UI.

Add the API Key to the Environment

First, copy and paste the Client access token from the main Agent page in DialogFlow.

DialogFlow is a free service with read-only access, so it is safe to use the Client Token in your frontend app. If using a paid API, you should not expose the token in Angular. A hacker could intercept this key and make requests to your endpoint. Check out my Firebase Cloud Functions API Proxy Server lesson to learn how fully secure API requsts.

Then app the api token your /environments/environment.ts file.

export const environment = {
production: false,

dialogflow: {
angularBot: 'YOUR_CLIENT_TOKEN'
}
};

Chat Service

The chat service will make the API call to Dialogflow. Here’s a breakdown of the important parts.

  1. Create a Message class to format messages
  2. Initialize Dialogflow (ApiAiClient) with your API token.
  3. Define a BehaviorSubject that is an array of messages.
  4. The converse method adds a user message to the array, then hits the API and updates the bot’s response in the same array.

chat.service.ts

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';

import { ApiAiClient } from 'api-ai-javascript';

import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

// Message class for displaying messages in the component
export class Message {
constructor(public content: string, public sentBy: string) {}
}

@Injectable()
export class ChatService {

readonly token = environment.dialogflow.angularBot;
readonly client = new ApiAiClient({ accessToken: this.token });

conversation = new BehaviorSubject<Message[]>([]);

constructor() {}

// Sends and receives messages via DialogFlow
converse(msg: string) {
const userMessage = new Message(msg, 'user');
this.update(userMessage);

return this.client.textRequest(msg)
.then(res => {
const speech = res.result.fulfillment.speech;
const botMessage = new Message(speech, 'bot');
this.update(botMessage);
});
}



// Adds message to source
update(msg: Message) {
this.conversation.next([msg]);
}

}

Dialog

Now we just need user interface that can send and receive messages.

chat-dialog.component.ts

To maintain a Observable array that can be appended with new vales, I am using the RxJS scan operator. Each time the BehaviorSubject in the service emits a new value, it will be concatenated to the previous value.

import { Component, OnInit } from '@angular/core';
import { ChatService, Message } from '../chat.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/scan';


@Component({
selector: 'chat-dialog',
templateUrl: './chat-dialog.component.html',
styleUrls: ['./chat-dialog.component.scss']
})
export class ChatDialogComponent implements OnInit {

messages: Observable<Message[]>;
formValue: string;

constructor(public chat: ChatService) { }

ngOnInit() {
// appends to array after each new message is added to feedSource
this.messages = this.chat.conversation.asObservable()
.scan((acc, val) => acc.concat(val) );
}

sendMessage() {
this.chat.converse(this.formValue);
this.formValue = '';
}

}

chat-dialog.component.html

In the HTML, we can now loop over the messages observable. I am using ngClass on each message to apply a conditional to/from class which is determined if the message came from a human or chatbot. Lastly, I set up a form with ngModel that sends the message when the enter key is pressed.

<h1>Angular Bot</h1>

<ng-container *ngFor="let message of messages | async">

<div class="message" [ngClass]="{ 'from': message.sentBy === 'bot',
'to': message.sentBy === 'user' }">
{{ message.content }}
</div>

</ng-container>


<label for="nameField">Your Message</label>


<input [(ngModel)]="formValue" (keyup.enter)="sendMessage()" type="text">

<button (click)="sendMessage()">Send</button>

chat-dialog.component.css

I’m using the Miligram CSS framework for some basic styling in this demo. Add the following lines to the index.html file to use them via CDN.

<!-- CSS Reset -->
<link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">

<!-- Milligram CSS minified -->
<link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">

I created some custom CSS styles to replicate the look of iOS text messages.

.message {
border-radius: 50px;
margin: 0 15px 10px;
padding: 15px 20px;
position: relative;
font-weight: bold;
}
.message.to {
background-color: #2095FE;
color: #fff;
margin-left: 100px;
text-align: right;
}
.message.from {
background-color: #E5E4E9;
color: #363636;
margin-right: 100px;

}
.message.to + .message.to,
.message.from + .message.from {
margin-top: -10px;
}

angular chatbot demo with DialogFlow

Next Steps

Congrats, you now have a basic Angular chatbot. There are many ways we can expand on this project. DialogFlow allows us to create far more complex intent-fulfillment cycles that can also trigger backend code.