Creating a Web Chat with SignalR, Web Sockets, Angular and Magic
With good abstractions everything is simple. Changing gears on your car probably involves thousands of moving parts, but still it's dead simple to achieve
Join the DZone community and get the full member experience.
Join For FreeWeb Sockets and asynchronous message transmissions over socket connections is probably one of the most difficult tasks you can possibly do in any web application. Hence, to make it manageable, it needs to be simplified, the same way the gearbox in your car is simple to use, even though it probably involves thousands of moving parts. In this article I will walk you through how to create a live chat client using SignalR, Web Sockets and Magic. If you wish to follow the tutorial though, the first thing you'll have to do is to download Magic.
The Angular frontend
First we'll need a frontend. Use the Angular CLI to create a new Angular project and name it "chat". Open a terminal on your development machine and execute the following command in it.
ng new chat
You can choose whatever setting you wish, but I chose the following as I went through the questions.
- No routing
- SCSS
Change into this folder in your terminal using cd chat, and use for instance Visual Studio Code to open the folder. If you have the Visual Studio Code command line binding, you can do this by typing code ./ into your terminal. Then we'll need a terminal window in VS Code. You can open a new terminal window through the "Terminal" menu item. Type the following command into your VS Code terminal window.
npm link
This ensures we link in all packaged our project depends upon. This might take some time. Afterwards we'll need to add the client side SignalR package. Execute the following command in your terminal window.
npm install @aspnet/signalr
Now we can serve our project with the following command.
ng serve --port 4201
The reason why we need to override the port, is because the Magic Dashboard is probably already running on the default port which is 4200. You can now visit localhost:4201 to see your app. Open the "app.component.html" file in the folder "/src/app/" and replace its existing code with the following HTML.
<div>
<h1>Chat client</h1>
<textarea id="" cols="30" rows="10" [(ngModel)]="message">{{message}}</textarea>
<br />
<button (click)="send()">Submit</button>
<br />
<textarea id="" cols="30" rows="10" [(ngModel)]="content"></textarea>
</div>
Notice, the UI isn't all that marvellous, but to keep the tutorial relevant to only SignalR and web sockets, we'll ignore this. If you wish to create a better UI you might benefit from reading up about Angular Material. However, if you saved your above HTML file, you've now got a couple of compiler errors. This is because we are using fields on our AppComponent class that still doesn't exist. Modify your "app.component.ts" file to resemble the following.
import {
Component,
OnDestroy,
OnInit
} from '@angular/core';
import {
HttpTransportType,
HubConnection,
HubConnectionBuilder
} from '@aspnet/signalr';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
content = '';
message = '';
hubConnection: HubConnection;
ngOnInit() {
let builder = new HubConnectionBuilder();
this.hubConnection = builder.withUrl('http://localhost:55247/sockets', {
skipNegotiation: true,
transport: HttpTransportType.WebSockets,
}).build();
this.hubConnection.start();
this.hubConnection.on('chat.new-message', (args) => {
this.content += JSON.parse(args).message + '\r\n\r\n';
});
}
ngOnDestroy() {
this.hubConnection.stop();
}
send() {
this.hubConnection.invoke(
'execute',
'/tutorials/add-chat',
JSON.stringify({
message: this.message,
}));
this.message = '';
}
}
Notice - You might have to change the URL above to http://localhost:5555/sockets if you installed Magic using the Docker images.
The above code ensure we are initialising SignalR as the component is initialised, and that we are disconnecting SignalR as the component is destroyed - In addition to that we are transmitting new chat messages over our SignalR connection as the user clicks the "Submit" button. Then we'll need to import the FormsModule which you can do by modifying your "app.module.ts" file to contain the following code.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
And we are done with our frontend. Save all your files, and let's move onwards to our backend.
Your Hyperlambda SignalR backend
If you click the "Submit" button, you will see that your console window gives you an error. This is because we have still not yet created our backend file responsible for executing as we submit chat messages to the server. Open up your Magic Dashboard with the localhost:4200 URL in a different browser window, log in with your root account, and open the "IDE" menu item. Then do exactly as follows.
- Click the "modules" folder
- Click the "New" button
- Type "tutorials" into the textbox
- Check the "Folder" checkbox to make sure we create a folder and not a file
- Click "Create"
This creates a new folder called "tutorials" inside your "modules" folder. Then do exactly as follows.
- Click the "tutorials" folder
- Click the "New" button
- Type "add-chat.socket.hl" into the textbox
- Click "Create"
This creates a new Hyperlambda file for you. This file will be resolved as the above send() Angular method is invoked over our SignalR web socket connection. Paste in the following into your file.
.arguments
message:string
unwrap:x:+/*/*
sockets.signal:chat.new-message
args
message:x:@.arguments/*/message
Then save your file, and switch back to your chat client browser window, and try writing something into the textarea and click "Submit".
Internals
As I started out with, web sockets are a bidirectional transport channel, implying we can both send and receive data over a socket. In the above send() method we are pushing data to the server. The above [sockets.signal] Hyperlambda slot transmits this message to all subscribers again. In our above Angular code we are subscribing to these messages with the following TypeScript.
this.hubConnection.on('chat.new-message', (args) => {
this.content += JSON.parse(args).message + '\r\n\r\n';
});
This ensures that all clients having connected to our web socket backend registering interest in the above messages, will be notified every time the message is published by our Hyperlambda. If you wish, you can open up multiple browser windows simultaneously and point them to localhost:4201, and write something into any chat, and see how the message instantly is received in all browser windows.
Endpoint resolving
One crucial point which separates the Hyperlambda web sockets implementation from other SignalR implementations, is the dynamic endpoint resolving. What this implies is that the file called "add-chat.socket.hl" is dynamically executed due to our invocation in the following Angular code.
this.hubConnection.invoke('execute', '/tutorials/add-chat', JSON.stringify({
message: this.message,
}));
Notice how the second argument resembles the relative path of the file. What the endpoint resolver will do, is roughly to add ".sockets.hl" to the relative URL specified, load this file dynamically, add the input arguments, and execute its Hyperlambda. This gives us a way to dynamically execute Hyperlambda files to respond to incoming SignalR messages. In addition it gives us the same method to declare arguments and pass in arguments to our SignalR invocations as we would use for normal HTTP REST invocations - Which of course makes it much simpler to consume and learn as you start out with web sockets in Magic.
Wrapping up
In this tutorial we created an Angular chat client. For transport we used SignalR, and on the server side we used dynamically resolved Hyperlambda files to receive messages. In addition we published our messages over a SignalR / Web Sockets connection, to allow multiple clients chat with each other in real time.
Published at DZone with permission of Thomas Hansen. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments