Writing your first app on Angular 2 — Part 3
We’ve reached the final part! So far we have an app that register the user and allows him to type messages, but we can’t sent this messages to anybody. In this last part we’ll create a server, which will be responsible for collect these messages and spread them across every person who has our app open.
Right now we have the following folder structure:
chat-app
— app
— boot.ts
— chat-app.component.ts
— chat-display.component.ts
— chat-input.component.ts
— bower_components
— node_modules
— package.json
— tsconfig.json
— bower.json
— index.html
If you are missing something give a look on part 1 and part 2.
Let’s start!
Creating the server
Before we start to think about our server, we need to be able to identify the user who wrote a given message. We’ll do it by creating a Message interface. Inside the app folder, create a file called message.ts and paste the following code on it:
export interface Message {
username: string;
content: string;
}
The code is self explanatory: it exports a interface called Message, which contains two fields: username and content, both of type string.
Now that we have our interface defined, we’ll create our server, using Express. Go to the chat-app folder and execute the following commands:
npm install express --save
npm install body-parser --save
The commands above will install Express and body-parser on our app. It also will add them in our package.json file as a dependency on our app. Still inside the chat-app folder, create a file called server.js and paste the following code on it:
"use strict";var express = require('express');
var bodyParser = require('body-parser');
var server = express();server.use(bodyParser.json());server.use('/', express.static(__dirname + '/'));var messages = [];server.post('/message', function(req, res) {
messages.push(req.body);
res.send(200);
});server.get('/messages', function(req, res) {
res.send(messages);
});server.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});server.listen(3000, '0.0.0.0', function() {
console.log('Chat app listening on port 3000!');
});
As Express is not the main goal of this post, I’m going to skip the technical details from it. What this code basically does is add a message to the array when you send a POST request and retrieves all the messages when you make a GET request.
One final change: Inside the chat-app folder, go to the package.json file and change the following command:
"start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
to this one:
"start": "concurrent \"npm run tsc:w\" \"node server.js\" "
This will run our new server instead of the lite-server we were running. After changing the code, start the server again, using this command:
npm start
Creating the service
Instead of letting the component manage the data, we’ll create an Angular service to do it. This way we can let our components only worry about updating the view, without have to thinking about where the data comes from. Inside the app folder, create a file called message.service.ts and paste the following code:
import {Injectable} from 'angular2/core';@Injectable()
export class MessageService {
}
This is the basic declaration of a service. It imports the Injectable decorator from Angular’s core package and exports the class MessageService. The Injectable decorator indicates that Angular would possibly have to inject other dependencies on MessageService. Right now we wouldn’t need it, but it is a good practice to always apply it from the beginning.
Since our server is expecting some HTTP requests we’ll need to add support to HTTP in our service. Go back to the message.service.ts and edit the code to look like this:
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';@Injectable()
export class MessageService { constructor(private _http: Http) {}}
We imported the Http class from the Angular’s http module and added it on our constructor as a parameter. As we are using the Injectable decorator, Angular knows it needs to add this dependency in our service.
The Angular Http module needs to be added on our HTML, otherwise Angular will not be able to recognize the Http class. To do it, go to the index.html file under the chat-app folder and add the following line inside the head tag:
<script src="node_modules/angular2/bundles/http.dev.js"></script>
There is one last thing we need to change: When we bootstrap our application we need to inform the HTTP provider. To do it, go to the boot.ts file inside the app folder and replace the existing code with this one:
import {bootstrap} from 'angular2/platform/browser'
import {ChatAppComponent} from './chat-app.component'
import {HTTP_PROVIDERS} from 'angular2/http';bootstrap(ChatAppComponent, [HTTP_PROVIDERS]);
In this new version we add the HTTP_PROVIDERS import from Angular http module and pass it to our bootstrap function. Now we can use the _http attribute anywhere inside our MessageService class.
Let’s start creating the function that sends a new message to our server:
import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
import {Message} from './message';@Injectable()
export class MessageService { constructor(private _http: Http) {}
insertMessage(message : Message) {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
this._http.post('message', JSON.stringify(message), {
headers : headers
}).subscribe();
}
}
The function insertMessage receives a Message, the interface we defined earlier, as a parameter. It then creates a header to specify we are sending data in the JSON format. Finally, it sends the POST request to our server in the url /message, using the JSON.stringify function to convert our interface to a string. We call the subscribe function to get the post to execute. The subscribe function will also return the result of the request, but since we’re not expecting anything we don’t need to pass any argument to the function.
The next function we will implement is to get all the messages available on the server. We would like if this function keeps requesting the data from server every 100 milliseconds, so every client connected on our chat would be updated in an acceptable time.
Go to the message.service.ts file and makes these changes on the code:
import {Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Rx';
import {Http, Headers} from 'angular2/http';
import {Message} from './message';@Injectable()
export class MessageService { constructor(private _http: Http) {}
insertMessage(message : Message) {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
this._http.post('message', JSON.stringify(message), {
headers : headers
}).subscribe();
}
getMessages() {
return Observable.interval(100).flatMap(() => this._http.get('messages'));
}
}
In the new getMessages function we’re using a class called Observable. This class contains a function called interval, which will produces a new value after each period. In our case, this period is 100 milliseconds. You may be wondering, which value is this going to produce as a return? Well, it will produce an Observable object, which will merge both our Observable.interval result and the _http.get observable in one. This is done using the flatMap function.
In other words: whoever call this function will receive an Observable object who makes a HTTP get request every 100 milliseconds.
Now that we finish our MessageService, it’s time to use it on our components.
Sending messages to the server
Let’s start using the service to send the message to our server. In the chat-input.component.ts file, import the service:
import {MessageService} from './message.service';
Now we need to tell the chat-input component that the MessageService exists. We do it buy declaring the providers property:
inputs : ['username'],
providers : [MessageService]
Before sending the message to the service, we need to store it in a variable. Let’s call it content.
private username = 'Default username';
private content : string;
Since the value of content shouldn’t be access by anyone outside or chat-input, we declare it as private. With the variable defined we can use two-way data binding to update the value when the user type his message. We need to change our input using ngModel like we did with the username if the Part 2 of the tutorial:
<input [(ngModel)]="content" class="form-control" type="text" placeholder="Insert your message here…">
Now the content variable is always with the last value that the user typed. The next step is to create the method which will send the message to the service. Inside the ChatInputComponent class, paste the following code:
constructor(private _messageService : MessageService) {}
sendMessage() {
this._messageService.insertMessage({
username : this.username,
content : this.content
});
this.content = '';
}verifyKey(event) {
if(event.keyCode == 13) {
this.sendMessage();
}
}
The same way we did with the Http service, we declared the MessageService in the constructor, allowing it to be used anywhere inside the class. Next we defined a function called sendMessage, who will invoke the insertMessage function, passing the username and the content he wrote. The last thing this function does is to clean the content variable, which imply in clean up the input on the screen, since we’re using two-way data binding. The verifyKey is the same from part 2: It only calls the sendMessage function when the user press the Enter key.
With the functions defined, we need to call it using our components. As we did in the part 2, we will use the keypress and click events to handle it:
<input [(ngModel)]="content" class="form-control" (keypress)="verifyKey($event)" type="text" placeholder="Insert your message here…">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" (click)="sendMessage()">Send message</button>
</span>
We are almost finished. The only thing left is update the display with the messages retrieved from the server.
Retrieving messages from the server
Since we are reading the messages, let’s start by importing the Message interface and the MessageService on our chat-display.component.ts file:
import {Message} from './message';
import {MessageService} from './message.service';
As we did in the chat-input component, we need to inform that we’re going to use the MessageService, declaring the providers property:
providers : [MessageService]
In our chat we are going to do something pretty simple regarding the messages: we will always retrieve all the messages from the server and discard the old ones.
Replace the ChatDisplayComponent class with the following code:
export class ChatDisplayComponent { private messages : Message[];
constructor(private _messageService : MessageService) {}
}
So far we’re doing the same we already did: Declaring a variable called messages to store the messages we retrieve from the server and the MessageService is being declared on the constructor to be used inside the class.
The next thing we want to do is to start retrieving the messages from the server as soon as the component initializes. The Angular team does not recommend to do it inside the constructor, so they’ve created a interface call OnInit. This interface have a ngOnInit method who will be called when the component initializes. Before using it, we need to import the interface:
import {Component, OnInit} from 'angular2/core';
Since it comes from the same package as Component, we can import both of them using one line. After import the OnInit interface, ChatDisplayComponent needs to implement it:
export class ChatDisplayComponent implements OnInit {
Finally we declare the ngOnInit method:
ngOnInit() {
var observable = this._messageService.getMessages();
observable.subscribe(res => {
this.messages = res.json();
});
}
Inside this function we are retrieving the Observable component we get from the getMessages function. We then call the subscribe function to start receiving responses from the server. The response comes as a string, so we convert it to JSON and then we add the result to our messages variable. When our component initializes the messages variable will be updated every 100 ms. The next step is display the result inside our component. To do it make the following changes on the template property:
template : `
<div class="chat-display thumbnail">
<div *ngFor="#message of messages">
<b>{{message.username}}</b>: {{message.content}}
</div>
</div>
`,
As you can see, we added a div with a *ngFor directive (please notice the * is part of the syntax and it is required). This directive will iterate trough every message stored on the messages variable and will append the username and the content inside the display.
That’s it, we finished the app!
If everything work as expected, we should have a fully functional chat app right now. If you want to look the final version of the code I publish it on my github, you can access it here. Here is a print from the final version:
I hope you enjoyed reading it as much as I enjoyed writing it :)
Until next time!