Part 7 from series: How to build a real-time chat app

Send Data with Angular Form Components

Create an Angular component to send a message to the GraphQL API

Our GraphQL mutation is ready for client integration. We can now build the user interface to send an anonymous user message to our GraphQL API, which will forward the request on to the Slack API. We will then be able to see new message(s) created by our Angular app on refresh of our application in the browser.

Before we can start using forms in Angular, we need to import the FormsModule into our application. From your project folder, open the file web-app/src/app/messages.module, then import and add the module.

File path icon web-app/src/app/messages.module
// Exisitng import code...
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    // Existing code...
  ],
  imports: [
    CommonModule,
    FormsModule, // <-- Add new module here
    MessagesRoutingModule
  ]
})
export class MessagesModule {}

Note: The order of imports is important.

Create the text field Angular component

We'll first look at the Typescript component we'll use to send a message. Primarily we'll need to import Apollo to get an instance of the Apollo client from our constructor. Then create a sendMessage method responsible for creating a GraphQL mutation query to be sent to our API.

From your project folder, open the web-app/src/app/messages/create-message/create-message.component.ts file and add the following code:

File path icon web-app/src/app/messages/create-message/create-message.component.ts
import { Component, OnInit } from '@angular/core';
import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
import conversationMessageFragment from '../shared/conversation-message-fragment.gql';

@Component({
  selector: 'app-messages-create-message',
  templateUrl: './create-message.component.html',
  styleUrls: ['./create-message.component.scss']
})
export class CreateMessageComponent implements OnInit {
  text: String = '';

  constructor(private apollo: Apollo) { }

  ngOnInit() {}

  sendMessage() {
    if (!this.text) {
      return false;
    }

    const textCache = this.text;
    this.text = '';

    this.apollo
      .mutate({
        mutation: gql`
          mutation createMessage {
            createMessage(text: "${textCache.replace('\n', '')}") {
              ...conversationMessage
            }
          }
          ${conversationMessageFragment}
        `
      })
      .subscribe();
  }
}

The structure of this component should look familiar. After the imports, we have metadata in the Component decorator to describe the location of compile-time assets and the selector name. At the top of the class CreateMessageComponent we define the text string property, which will be the model of the input text field and hold the value of the user-typed message.

The constructor gives us the instance of our Apollo client. We don't need the ngOnInit method but it's automatically generated by the CLI and have left it in.

The sendMessage method is called when a user indicates that they've finished typing and want to send the message. The method has a guard that stops an empty message getting sent. If a message isn't empty, it will cache the message and instantly reset the text field whilst the message is being sent.

Finally we make a call to the GraphQL API with the gql mutation query in the request body (through an observable subscription). This defines the mutation createMessage with a text argument that is supplied with a tidied string. Lastly a fragment that we previously used to define a message body in the request is reused here with ...conversationMessage.

Create the HTML template for writing a message

We have the behaviour of our component all ready to go, lets now create the layout of our text field and bind it to our component.

From the same folder, open the file: create-message.component.html and replace the contents with the following code.

File path icon web-app/src/app/messages/create-message/create-message.component.html
<div class="messages-create-message">
  <textarea class="messages-create-message__input"
    [(ngModel)]="text"
    (keyup.enter)="sendMessage()"></textarea>
  <button class="messages-create-message__send" (click)="sendMessage()">Send message</button>
</div>

Again, we've used BEM to style the component and determine our class structure. And also added a textarea form control that is bound to the Typescript components text property with the syntax [(ngModel)]="text".

Notice the use of both square brackets and parentheses [()] to wrap ngModel. This indicates two-way model binding within the component so that not only will the components text property get updated when the user types in the text field but the words typed in the textarea can also be updated as a result of the components behaviour. This allows the code this.text = ''; within the sendMessage method to delete all the words that a user has entered so they can start typing a new message.

We use event binding (keyup.enter)="sendMessage()", which will call the sendMessage method when a user hits the 'enter' key.

As well as typing 'enter', a user can also click the 'Send message' button, which will call the same method through a click event binding.

Create the styles for the text field and button

To complete our component, we just need to add the styles. Within the same folder, Open the file create-message.component.scss and add the following:

File path icon web-app/src/app/messages/create-message/create-message.component.scss
.messages-create-message {
  padding : .5rem .5rem 1rem .5rem;
  background-color : #f5f5f5;

  * {
    box-sizing: border-box;
    font-size : 1rem;
    font-family: 'Helvetica Neue', Arial;
  }

  &__input {
    min-height : 5rem;
    width : 100%;
    padding : .6rem;
    border-radius : 5px;
    border : 1px solid #999;

    &:focus,
    &:active {
      border-color : #004586;
      outline : none;
      box-shadow: 0 0 0 3px #0385ff;
    }
  }

  &__send {
    margin-top : .5rem;
    padding : .6rem 1rem;
    font-size : 1.2rem;
    font-weight : normal;
    color : #fff;
    background-color : #0099ff;
    border : none;
    border-radius : 3px;
    cursor : pointer;
  }
}

That's everything we need to send a message from the application!

Navigate to the Angular application in your browser, type a message in the new component and send. When you refresh the page, you should see your messages at the bottom of the list. It will also appear in systems notifications that have been enabled from any downloaded Slack apps you might have installed for your laptop or mobile phone when logged in to the same Slack workspace.

Note: The application is setup to show a maximum of 4 messages at a time. This can be changed within the max variable of the query defined in the view-conversation.component.ts component.

At the moment we have to refresh the browser window in order to see new messages that we or anyone else created, lets fix that by using a subscription.