
Compartir:
Alex Lakatos es JavaScript Developer Advocate para Nexmo. En su tiempo libre es voluntario en Mozilla como Tech Speaker y Reps Mentor. Desarrollador de JavaScript en la web abierta, ha estado empujando sus límites todos los días. Cuando no está programando en Londres, le gusta viajar por el mundo, así que es probable que te lo encuentres en la sala de espera de un aeropuerto.
Crea una aplicación de chat con Angular Material y Vonage
Tiempo de lectura: 26 minutos
Nota: Es posible que algunas de las herramientas o métodos descritos en este artículo ya no reciban soporte o no estén actualizados. Para obtener contenido actualizado o soporte, consulta nuestras últimas publicaciones o contáctanos en el Slack de la comunidad de Vonage
En este tutorial, habilitaremos el chat en una aplicación web Angular utilizando el SDK de JavaScript y la Conversation API para que los usuarios puedan comunicarse en nuestra aplicación. Si deseas ver el código fuente, se encuentra en nuestra página de Vonage Community GitHub de Vonage.
Esto es lo que intentamos construir:

Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Antes de empezar
Antes de empezar, necesitarás algunas cosas:
Conocimientos básicos de Angular
Node.js instalado en su máquina.
El código del middleware desde GitHub.
La CLI de Vonage. Instálalo de la siguiente manera:
npm install -g @vonage/cliConfigura la CLI para usar tu clave y secreto de API de Vonage:
vonage auth set –api-key=’VONAGE_API_KEY –api-secret=’VONAGE_API_SECRET’ Obtenga el código del middleware en GitHub
En primer lugar, vamos a clonar el código fuente del middleware e instalar sus dependencias. Vamos a escribir una aplicación Node.js usando Express que proporciona un nivel de abstracción entre la API de Vonage y el código Angular:
git clone https://github.com/Nexmo/stitch-demo.git
cd stitch-demo
npm install Ejecutar el código Middleware desde GitHub
Antes de poder ejecutar el código, necesitaremos crear una aplicación RTC dentro de la plataforma de Vonage para usar dentro de este código:
La salida del comando anterior será algo parecido a esto:
El primer elemento es el ID de Applications, del que debe tomar nota (nos referiremos a él como APP_ID más adelante). El último valor es la ubicación de la clave privada. La clave privada se utiliza para generar JWT que autentican tus interacciones con Vonage.
Ahora, tendremos que hacer una copia de example.env y llamarla .envy actualizar los valores dentro de tu Vonage API_KEY y API_SECRET. También tendremos que añadir el APP_ID que acabamos de generar la ruta a tu clave privada. Después de haber actualizado los valores, podemos ejecutar el código en modo de depuración con:
Crear usuarios y conversaciones
La aplicación debe ejecutarse en localhost:3000. Ahora que la aplicación se está ejecutando, vamos a seguir adelante y crear un par de usuarios y una conversación, y luego vamos a añadir los usuarios que hemos creado a la conversación.
Crearemos un par de usuarios ejecutando este comando dos veces, una con el nombre de usuario alice y luego con jamie:
La salida debería ser similar a:
Anotaremos el ID de usuario y nos referiremos a él más adelante como USER_ID. Ahora, vamos a crear una conversación a través de la API de demostración:
La salida debería ser similar a:
Anotaremos el ID de la conversación y nos referiremos a ella más adelante como CONVERSATION_ID. Ahora, vamos a unir a los usuarios en la conversación. Vamos a ejecutar el siguiente comando dos veces-recuerda sustituir los caracteres CONVERSATION_ID y USER_ID por los IDs de los dos pasos anteriores cada vez que ejecutes este comando:
Generar aplicación Angular
Ahora que tenemos nuestro middleware en funcionamiento, es el momento de crear la aplicación Angular. Vamos a utilizar el Angular CLI para generar la aplicación, así que si no lo tienes instalado, necesitarás instalarlo primero:
A continuación, lo utilizaremos para generar una nueva aplicación con enrutamiento. Puede tardar un poco en generar todos los archivos e instalar las dependencias:
Añadir Material Design
Una vez finalizado el comando anterior, añadiremos Material Angular y sus dependencias al proyecto:
También tendremos que importar el NgModule para cada componente que queramos utilizar en nuestra aplicación. Para ello, tenemos que abrir src/app/app.module.ts en nuestro editor e importar los módulos de la parte superior:
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import {
MatAutocompleteModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatStepperModule
} from '@angular/material';También tendremos que actualizar la declaración @NgModules para añadir los módulos anteriores:
@NgModule({
...
imports: [
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
FormsModule,
HttpClientModule,
MatTabsModule,
MatCardModule,
MatGridListModule,
MatButtonModule,
MatInputModule,
MatListModule,
MatIconModule,
MatSidenavModule,
MatProgressSpinnerModule,
MatTooltipModule,
MatDialogModule
],
],
...
})También vamos a querer añadir un tema a Material, por lo que añadir esta línea a su style.css:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";Si queremos utilizar los Material Design Iconstendremos que cargar la fuente de los iconos en nuestro archivo index.html:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> Polyfill Vonage Client SDK para JavaScript
Ahora que tenemos nuestra aplicación Angular generada y todo configurado con Material, instalaremos el Vonage Client SDK para JavaScript y lo añadiremos al final de polyfills.ts:
/**********************************************************
* APPLICATION IMPORTS
*/
import 'nexmo-conversation'; Servicio de mensajería
Necesitaremos crear un Servicio Angular para manejar los datos de nuestro middleware. Vamos a generarlo usando el CLI de Angular:
En nuestra carpeta src/app carpeta, messaging.service.spec.ts y messaging.service.spec.ts. Vamos a actualizar el archivo messaging.service.spec.ts para agregar el ConversationClient del Vonage In-App SDK, instanciar un cliente y manejar la obtención de un usuario Token Web JSON del middleware. Vamos a reemplazar el código repetitivo con:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
declare var ConversationClient: any;
const GATEWAY_URL = "http://localhost:3000/api/";
@Injectable()
export class MessagingService {
constructor(private http: HttpClient) {
}
initialize() {
this.client = new ConversationClient(
{
debug: false
}
)
}
public client: any
public app: any
public getUserJwt(username: string): Promise<any> {
return this.http.get(GATEWAY_URL + "jwt/" + username + "?admin=true").toPromise().then((response: any) => response.user_jwt)
}
}
Debemos actualizar app.module.ts para importar el MessagingService y registrarlo como proveedor:
import { MessagingService } from './messaging.service';
...
providers: [MessagingService],
... Componente de inicio de sesión
Empecemos a construir algo de UI para nuestra aplicación. Empezaremos con un LoginComponent, y lo generaremos con el CLI de Angular:
Esto generará una carpeta login dentro de la carpeta app y cuatro archivos para el código HTML, CSS y TypeScript, así como las pruebas. Reemplacemos el código en login.component.html con una interfaz de usuario para iniciar sesión. Elegí un <mat-grid-list> con un <mat-card> dentro de él, y un formulario con un botón de inicio de sesión que llama a onLogin() cuando se envía. El código se ve así:
<mat-grid-list cols="4" rowHeight="100px">
<mat-grid-tile colspan="1" rowspan="5"></mat-grid-tile>
<mat-grid-tile colspan="2" rowspan="1"></mat-grid-tile>
<mat-grid-tile colspan="1" rowspan="5"></mat-grid-tile>
<mat-grid-tile colspan="2" rowspan="3">
<mat-card class="mat-typography login">
<h1>Login</h1>
<form (ngSubmit)="onLogin()">
<mat-form-field class="full-width">
<input matInput placeholder="Username" name="username" [(ngModel)]="username">
</mat-form-field>
<br>
<button type="submit" mat-raised-button color="primary" (click)="showSpinner = !showSpinner">Login
<mat-spinner color="accent" mode="indeterminate" *ngIf="showSpinner"></mat-spinner>
</button>
</form>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
Vamos a añadirle algo de CSS para el spinner Material en el archivo login.component.css archivo:
.login {
text-align: center;
}
.mat-spinner {
width: 20px !important;
height: 20px !important;
display: inline-block;
margin-left: 10px;
}
/deep/ .mat-spinner svg{
width: 20px !important;
height: 20px !important;
}También tenemos que actualizar login.component.ts para añadirle el MessagingService e implementar el método onLogin() método. El método tomará el nombre de usuario, realizará una solicitud a través del servicio de mensajería al middleware que estamos ejecutando para obtener un JWT de usuario y luego lo utilizará para autenticar a través del método login del SDK de Vonage In-App JavaScript. El código es el siguiente:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { MessagingService } from '../messaging.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
username: string = ""
constructor(private ms: MessagingService, private router: Router) { }
ngOnInit() {
this.ms.initialize()
}
onLogin() {
this.ms.getUserJwt(this.username).then(this.authenticate.bind(this))
}
authenticate(userJwt: string) {
this.ms.client.login(userJwt).then(app => {
this.ms.app = app
this.router.navigate(['/conversation']);
})
}
}
Fíjate que he importado el Router de Angular, lo he inyectado en nuestro constructor, y lo estoy usando al final del flujo de autenticación para navegar a la siguiente página, /conversation.
Componente de conversación
Todavía no hemos creado el componente conversation así que usemos la CLI de Angular para crearlo:
Vamos a actualizar el archivo conversation.component.html para utilizar un material sidenav que lista las conversaciones de los usuarios a la izquierda y los miembros de la conversación a la derecha, dejando el centro para nuestro chat principal. Vamos a añadir una cabecera a la sección de chat para listar el nombre de la conversación y el número de miembros, y una sección de entrada se añadirá en la parte inferior. Dejaremos la sección central para mostrar el historial de conversaciones. Construiremos un shell vacío por ahora y lo añadiremos más tarde a medida que desarrollemos el módulo ConversationComponent. El HMTL debería tener este aspecto:
<mat-sidenav-container class="container">
<mat-sidenav mode="side" opened>
<mat-card>
<mat-tab-group>
<mat-tab>
<ng-template mat-tab-label>
<mat-icon matListIcon>forum</mat-icon>
</ng-template>
<mat-list class="conversations">
...
</mat-list>
</mat-tab>
</mat-tab-group>
</mat-card>
</mat-sidenav>
<mat-sidenav position="end" mode="side" opened *ngIf="selectedConversation">
<mat-card>
<mat-list class="members">
...
</mat-list>
</mat-card>
</mat-sidenav>
<section class="empty-conversation" *ngIf="!selectedConversation">
<h1 class="mat-display-1">Select a conversation from the left to start chatting</h1>
</section>
<section *ngIf="selectedConversation">
<div class="mat-typography conversation-header">
...
</div>
<mat-divider></mat-divider>
<mat-list dense class="conversation-history mat-typography">
...
</mat-list>
<div class="conversation-input">
<mat-divider></mat-divider>
<mat-form-field class="full-width">
<input matInput placeholder="Start chatting..." name="text" [(ngModel)]="text">
<mat-icon matSuffix (click)="">send</mat-icon>
</mat-form-field>
</div>
</section>
</mat-sidenav-container>
Vamos a actualizar el archivo conversation.component.ts con el boilerplate necesario para los métodos que vamos a utilizar más adelante:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/map';
import { MessagingService } from '../messaging.service';
@Component({
selector: 'app-conversation',
templateUrl: './conversation.component.html',
styleUrls: ['./conversation.component.css']
})
export class ConversationComponent implements OnInit {
constructor(private ms: MessagingService, private router: Router) { }
buildConversationsArray(conversations) {
}
ngOnInit() {
}
selectConversation(conversationId: string) {
}
sendText(text: string) {
}
conversations: any
selectedConversation: any
text: string
events: Array<any> = []
}
Empecemos implementando ngOnInit() para que compruebe si tenemos datos de la aplicación antes de intentar getConversations utilizar el SDK In-App. Si no hay datos de la aplicación, entonces seremos redirigidos a la pantalla de inicio de sesión:
ngOnInit() {
if (!this.ms.app) {
this.router.navigate(['/']);
} else {
this.ms.app.getConversations().then(conversations => {
this.conversations = this.buildConversationsArray(conversations)
})
}
}
Necesitamos implementar un método de ayuda para construir una matriz de conversaciones a partir del diccionario de conversaciones que proporciona el SDK de JavaScript In-App para que podamos utilizarlo con *ngFor en la interfaz de usuario:
buildConversationsArray(conversations) {
let array = [];
for (let conversation in conversations) {
array.push(conversations[conversation]);
}
return array
}Agreguemos un método para seleccionar una conversación de la lista. Necesitaremos tomar el ID de la vista, pasarlo al controlador y luego usar el SDK de Vonage para obtener datos sobre la conversación. Almacenaremos esto en una propiedad de la clase para que esté disponible para ver más adelante. También usaremos Observable para crear un array a partir de conversation.events Map para que podamos recrear el historial de chat cuando el usuario regrese a la aplicación. También añadiremos un listener de eventos usando el SDK para escuchar eventos y añadirlos a la lista de eventos. text y añadirlos al historial de eventos:
selectConversation(conversationId: string) {
this.ms.app.getConversation(conversationId).then(conversation => {
this.selectedConversation = conversation
Observable.from(conversation.events.values()).subscribe(
event => {
this.events.push(event)
}
)
this.selectedConversation.on("text", (sender, message) => {
this.events.push(message)
})
console.log("Selected Conversation", this.selectedConversation)
}
)
}
Por último, agreguemos un método que tome la entrada de la vista y la envíe a la API de Vonage en la aplicación a través del SDK:
sendText(text: string) {
this.selectedConversation.sendText(text).then(() => this.text = "")
}
Ahora que hemos implementado todos los métodos que necesitamos, podemos volver atrás y dar más cuerpo a la vista para utilizar los modelos de datos que hemos creado en el controlador. En primer lugar, vamos a actualizar la sección conversations en conversation.component.html:
...
<mat-list class="conversations">
<mat-list-item *ngFor="let conversation of conversations" (click)="selectConversation(conversation.id)">
<mat-icon matListIcon>forum</mat-icon>
<p>{{conversation.display_name}}</p>
</mat-list-item>
</mat-list>
...
Ahora vamos a añadir la sección de miembros:
...
<mat-list class="members">
<mat-list-item *ngFor="let member of selectedConversation.members | keys">
<p>{{member.value.user.name}}</p>
</mat-list-item>
</mat-list>
...
Estamos utilizando una tubería llamada keys para transformar el objeto members que obtenemos del SDK en un array, así que tendremos que crearlo usando la CLI de Angular y actualizar el archivo keys.pipe.ts generado:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'keys'
})
export class KeysPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push({key: key, value: value[key]});
}
return keys;
}
}A continuación, actualizaremos la sección conversation-header de la vista para mostrar el nombre de la conversación seleccionada y el número de miembros:
...
<div class="mat-typography conversation-header">
<h2>
<mat-icon>forum</mat-icon>
{{selectedConversation.display_name}}</h2>
<p>
<mat-icon>account_circle</mat-icon>
{{(selectedConversation.members | keys).length}} Members</p>
</div>
...También tenemos que actualizar la sección conversation-history para analizar los eventos y recrear el historial en el chat. Los eventos procedentes del SDK de la aplicación tienen varios tipos, por lo que tendremos en cuenta algunos de ellos, como member:joined y text:
...
<mat-list dense class="conversation-history mat-typography">
<mat-list-item *ngFor="let event of events; index as i" [dir]="event.from === selectedConversation.me.id ? 'rtl' : 'ltr'">
<img *ngIf="event.type == 'text'" matListAvatar matTooltip="{{selectedConversation.members[event.from].user.name}}" src="https://randomuser.me/api/portraits/thumb/lego/{{i}}.jpg"
/>
<p *ngIf="event.type == 'text'" [dir]="'ltr'">{{event.body.text}}</p>
<p *ngIf="event.type == 'member:joined'" class="text-center">
<b>{{selectedConversation.members[event.from].user.name}}</b> has joined the conversation</p>
</mat-list-item>
</mat-list>
...
Tendremos que actualizar la parte conversation-input para poder enviar mensajes a la conversación:
...
<div class="conversation-input">
<mat-divider></mat-divider>
<mat-form-field class="full-width">
<input matInput placeholder="Start chatting..." name="text" [(ngModel)]="text">
<mat-icon matSuffix (click)="sendText(text)">send</mat-icon>
</mat-form-field>
</div>
...
Vamos a añadirle algo de CSS para que sea una pantalla completa y de posición fija. Añade el siguiente CSS a conversation.component.css:
.container {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.mat-drawer.mat-drawer-side {
padding: 0 5px;
}
.empty-conversation {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.conversation-header h2, .conversation-header p {
align-items: center;
display: flex;
}
.text-center {
text-align: center;
width: 100%;
}
.conversation-history.mat-list {
height: calc(100% - 180px);
overflow-x: scroll;
position: absolute;
width: 100%;
}
.conversation-history.mat-list p {
margin: 0;
}
.empty-conversation h1 {
margin: 0;
}
.conversations .mat-list-item {
cursor: pointer;
}
.mat-card {
height: 100%;
padding: 0 24px;
overflow: scroll;
}
.conversation-input {
position: absolute;
bottom: 0;
width: 100%;
background-color: #fafafa;
}
section .mat-list .mat-list-avatar{
width: 25px;
height: 25px;
}
.mat-list-avatar {
margin: 0 5px;
}
.right {
text-align: right;
}
.full-width {
width: 100%;
}
.full-width .mat-icon {
cursor: pointer;
}Por último, pero no menos importante, tenemos que actualizar el módulo de enrutamiento de la aplicación en app-routing.module.ts para que se muestren las rutas correctas:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { ConversationComponent } from './conversation/conversation.component';
const routes: Routes = [
{
path: '',
component: LoginComponent,
},
{
path: 'conversation',
component: ConversationComponent,
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
],
declarations: []
})
export class AppRoutingModule { }También tenemos que sustituir todo el app.component.html por el <router-outlet> para mostrar el router en la primera página:
<router-outlet></router-outlet> Ejecute su aplicación
Después de hacer la aplicación detallada en este post, ejecute la aplicación para ver que funciona:
La aplicación se ejecutará en "http://localhost:4200". Yo sugeriría abrir la aplicación en dos pestañas separadas, iniciar sesión con ambos alice y jamie y ¡empezad a hablar entre vosotros! Si quieres ver la aplicación en su estado final, puedes consultar el código fuente de esta aplicación en nuestra página GitHub de la comunidad. Si quieres ver una versión más avanzada de este código, puedes consultar el stitch-demo que descargaste al principio de la entrada del blog. También contiene un front-end Angular Material.
¿Y ahora qué?
Si deseas continuar aprendiendo a usar Vonage Client SDK para JavaScript, consulta nuestro inicio rápido, donde te mostramos cómo hacerlo:
utilizar más escuchadores de eventos para mostrar el historial de chat, y cuando un usuario está escribiendo
¿Tienes alguna pregunta o algo que compartir? Únete a la conversación en Slack de la comunidad de Vonagey mantente actualizado con el Boletín para desarrolladoressíguenos en X (antes Twitter)suscríbete a nuestro canal de YouTube para ver tutoriales en video, y sigue la página de página para desarrolladores de Vonage en LinkedInun espacio para que los desarrolladores aprendan y se conecten con la comunidad. Mantente conectado, comparte tu progreso y entérate de las últimas noticias, consejos y eventos para desarrolladores.
Compartir:
Alex Lakatos es JavaScript Developer Advocate para Nexmo. En su tiempo libre es voluntario en Mozilla como Tech Speaker y Reps Mentor. Desarrollador de JavaScript en la web abierta, ha estado empujando sus límites todos los días. Cuando no está programando en Londres, le gusta viajar por el mundo, así que es probable que te lo encuentres en la sala de espera de un aeropuerto.
