BFFT Techblog Januar: Node how to Express yourself with Angular
01.08.2017

BFFT Techblog: Node how to Express yourself with Angular

Thema: Wie man mit Node.js, AngularJS und Socket.IO eine Anwendung zur Visualisierung von Echtzeitdaten entwickelt.

Nachfolgend möchte ich kurz die einzelnen Technologien und Tools ansprechen mit denen schnell eine Web-Anwendung zur Darstellung von Echtzeitdaten gebaut werden kann. Die Details zur Implementierung können in einem Demo-Projekt betrachtet werden. Der Quellcode liegt auf Github.

Node.js

Die Skriptsprache JavaScript ist auf Seiten des Webclients in den letzten Jahren praktisch zum Standard geworden und hat sich gegen Konkurrenten wie Flash und Java durchgesetzt. Mit der JavaScript Laufzeitumgebung Node.js1 hat sich ein großes Ökosystem gebildet um JavaScript auch serverseitig verwenden zu können.

Node.js arbeitet mit einem einzigen Thread und verwendet nicht blockierende I/O Aufrufe. Dadurch können tausende parallele Verbindungen in einem Thread verarbeitet werden. Klassische Webserver wie der Apache Webserver erstellen im Gegensatz zu Node.js für jede Verbindung einen neuen Thread. Jeder dieser Threads belegt Hauptspeicher. Kontextwechsel zwischen den Threads und die Verwaltung binden Ressourcen.

Aufgrund der ereignisgesteuerten Architektur und der asynchronen Verarbeitung von I/O-Aufgaben eignet sich Node.js hervorragend für echtzeitfähige Webanwendungen mit vielen I/O Operationen. Um die ereignisgesteuerte Architektur von Node.js richtig ausnutzen zu können muss jede Funktion die I/O Operationen ausführt Callbacks verwenden. Das Paradigma der ereignisgesteuerten Programmierung ist anzuwenden, da es ansonsten zu Blockaden kommt.

Ein mit Node.js implementierter Webserver ist ressourcensparend und läuft auch auf Geräten mit leistungsschwachen Prozessoren. Die Node.js Laufzeitumgebung ist auf vielen Betriebssystemen lauffähig u. a. auch auf dem Raspberry Pi oder dem TTX DataLogger2 von TTTech.

Ein einfacher und ressourcenschonender Webserver lässt sich schnell mit Node.js und der Express Bibliothek implementieren.

npm Packet Manager

Die Node.js Laufzeitumgebung ist sehr leichtgewichtig und beinhaltet nur die wichtigsten Funktionen. Die Funktionalität lässt sich aber beliebig durch Zusatzpakete erweitern die über den npm3 Paket Manager bezogen werden können. Es besteht ebenfalls die Möglichkeit eigene Pakete zu erstellen und diese der Community über npm zur Verfügung zu stellen.

Jedes Node.js Projekt hat eine package.json Datei in der das Projekt konfiguriert wird. In dieser Konfigurationsdatei können benötigte Zusatzpakete als Dependency eingetragen werden. Über den Kommandozeilenbefehl npm install lassen sich die Pakete herunterladen und in das Node.js Projekt einbinden. npm erstellt im Verzeichnis node_modules für jedes Paket einen eignen Ordner. Die Pakete lassen sich dann per require(“paketname“) laden.

package.json
 {
 "name": "bfft_blog",
 "version": "1.0.0",
 "description": "Demo application to visualize real-time data with AngularJS, Node.js and Socket.IO",
 "main": "server.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "dependencies": {
 "body-parser": "^1.15.2",
 "bower": "^1.8.0",
 "ejs": "^2.5.2",
 "express": "^4.14.0",
 "express-session": "^1.14.2",
 "morgan": "^1.7.0",
 "socket.io": "^1.6.0"
 },
 "author": "Tobias Dörner",
 "license": "MIT",
 "repository": {
 "type": "git",
 "url": "https://github.com/bfft/201701_techblog_angular_node.git"
 }
 }

Socket.IO

Die JavaScript Bibliothek Socket.IO4 ermöglicht eine echtzeitfähige, bidirektionale Kommunikation zwischen Webclient und Webserver. Für die Kommunikation wird das WebSocket Protokoll verwendet. Die Socket.IO Bibliothek kapselt das WebSocket Protokoll und nutzt Fallbackoptionen im Falle, dass Clients das WebSocket Protokoll nicht unterstützen.

Express

Express5 ist ein sehr flexibles Framework für Node.js Web Applikationen, das sich durch Middleware nahezu beliebig erweitern lässt. Ein leichtgewichtiger HTTP-Server der z. B. statische HTML, CSS und JavaScript Dateien an einen Client ausliefert lässt sich damit mit wenigen Codezeilen umsetzen. Genauso einfach kann auch Socket.IO eingebunden werden um mit Clients per WebSockets zu kommunizieren, siehe Demo-Projekt.

AngularJS

Für die Entwicklung von Single-page-Webanwendungen haben clientseitige JavaScript-Webframeworks wie AngularJS, in den letzten Jahren einen starken Hype erfahren. AngularJS6 wird von Google als Opensource unter der MIT License entwickelt. Die Version 1.x ist mittlerweile gut dokumentiert und auch für den produktiven Einsatz ausgereift. Eine große Community steht bei Problemen zur Verfügung. AngularJS wird u. a. von Netflix, Vevo, Sky Store und Google Mail verwendet.

Mit Hilfe des Frameworks lassen sich die einzelnen HTML-Ansichten clientseitig generieren. Bei einer klassischen AngularJS Applikation arbeitet man serverseitig mit einer REST-Schnittstelle die JSON konsumiert und produziert. Clientseitig wird dann die REST-Schnittstelle abgefragt über welche die relevanten Daten geholt werden. Die HTML Seite wird anschließend von der AngularJS Applikation direkt im Webclient erzeugt. Manipulationen am DOM via jQuery werden überflüssig.

Das two-way data binding ist eines der wichtigsten Features von AngularJS. Der Scope-Service erkennt Änderungen im Model und aktualisiert die View mit Hilfe von Expressions im HTML. Umgekehrt werden Änderungen auf der View auch direkt im Model aktualisiert.

AngularJS verwendet das Model-View-Viewmodel (MVVM) Software-Architektur-Pattern. Die Webapplikation lässt sich mit Hilfe von Modulen, Controllern, Services, View-Templates, Scopes etc. strukturieren. Dependency Injection ist fest in AngularJS integriert, die Testbarkeit der einzelnen Komponenten ist damit möglich. Mit Hilfe von Tools wie Karma7 und Jasmine8 können Tests erstellt und ausgeführt werden.

Bower

Bower9 ist ein Paketmanager für JavaScript Frameworks, Bibliotheken und Assets die im Webclient verwendet werden wie z. B. jQuery, AngularJS oder Bootstrap. Bower wird ähnlich verwendet wie npm. In einer Konfigurationsdatei mit dem Namen bower.json werden die benötigten Pakete definiert. Über eine weitere Konfigurationsdatei mit dem Namen .bowerrc lässt sich das Zielverzeichnis, in das die Pakete heruntergeladen werden, spezifizieren.

bower.json
{
  "name": "201701_techblog_angular_node",
  "version": "1.0.0",
  "description": "Demo application to visualize real-time data with AngularJS, Node.js and Socket.IO",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "body-parser": "^1.15.2",
    "bower": "^1.8.0",
    "ejs": "^2.5.2",
    "express": "^4.14.0",
    "express-session": "^1.14.2",
    "morgan": "^1.7.0",
    "socket.io": "^1.6.0"
  },
  "author": "Tobias Dörner",
  "license": "MIT",
  "repository": {
        "type": "git",
        "url": "https://github.com/bfft/201701_techblog_angular_node.git"
  },
  "private": true
}
.bowerrc
{
  "directory": "public/libs"
}

Der Konsolenbefehl bower install lädt die spezifizierten Pakete und ihre Abhängigkeiten herunter und erstellt im angegebenen Zielverzeichnis für jedes Paket ein Verzeichnis.

 

Abbildung 1: Konsolenausgabe von Bower

Die Verwendung von Paketmanagern, wie Bower und npm, hat den großen Vorteil, dass wir die Bibliotheken nicht mehr ins Repository einchecken müssen. Der Paketmanager löst die Abhängigkeiten der Pakete ebenfalls selbstständig auf. AngularJS beispielsweiße benötigt jQuery in einer bestimmten Versionsnummer. Diese wird von Bower automatisch heruntergeladen der Entwickler muss sich darum nicht kümmern.

Bootstrap

Mit dem wachsenden Markt an Smartphones und Tablets steigt auch die Zahl an Usern die Webapplikationen über mobile Geräte bedienen. Entwickler stehen nun vor der Herausforderung Webapplikationen zu entwickeln die sich sowohl auf Desktop-Geräten wie auch den mobilen Geräte einfach bedienen lassen.

Mit Hilfe des Open-Source CSS Frameworks Bootstrap 10, das von Twitter stammt, können Webseiten im Sinne des responsive Webdesigns entwickelt werden. Bootstrap enthält Gestaltungsvorlagen für Typografie, Formulare, Navigationsleisten, Tabellen, Schaltflächen.
Bootstrap eignet sich besonders, wenn in kurzer Zeit ein Prototyp entwickelt werden muss und wenig Zeit für ein eigenes Oberflächendesign zur Verfügung steht. Bootstrap ist dennoch so flexibel, dass es sich über Themes an die eigenen Bedürfnisse anpassen lässt.

Rickshaw

Rickshaw11 ist eine Open-Source JavaScript Bibliothek um interaktive Graphen und Charts darzustellen. Die Bibliothek baut auf dem d3 Framework auf. Die Graphen werden in SVG Elementen gezeichnet und lassen sich per CSS kustomisieren. Rickshaw ermöglicht schnell ohne weitere d3 Kenntnisse ansprechende Graphen und Charts zu zeichnen mit dem Nachteil, dass Rickshaw seine Graphen nur in SVG Elementen rendern kann. Dies ist nicht immer von Vorteil.

Der HTML 5 Standard kennt Canvas und SVG Elemente. Innerhalb dieser Elemente lassen sich Objekte rendern. Canvas wie auch SVG werden von d3 unterstützt. Wenn man beispielsweise für mehrere tausend Elemente ein SVG Element verwenden möchte wird der Web-Browser mit hoher Wahrscheinlichkeit in die Knie gehen. Canvas ist zu bevorzugen wenn viele Elemente gerendert werden müssen.

Demo

Um das Zusammenspiel der oben genannten Technologien, Frameworks und Bibliotheken zu demonstrieren habe ich mir ein kleines Beispiel überlegt.

Klassischerweise kommuniziert die mit AngularJS entwickelte Webapplikation über eine REST-Schnittstelle mit dem Server. In dieser Demo geht es speziell darum wie mit AngularJS Daten in Echtzeit über Socket.IO zwischen Client und Server ausgetauscht werden können.

Die Demo-Anwendung ist in zwei Bereiche unterteilt, einmal einen Client der beim User im Browser läuft und einem leichtgewichtigen Server der auch auf einer ressourcenarmen Plattform lauffähig ist. Der Server kann über ein Modul die aktuelle Auslastung der CPU abfragen und diese in Echtzeit an den Client übermitteln. Der Client stellt die in Echtzeit ankommenden Daten textuell und als Graph für den Benutzer dar. Die Anwendung kann sowohl per Web-Browser als auch über mobile Geräte wie Smartphones und Tablets bedient werden.

Abbildung 2: Browseransicht über einen Desktop-PC

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Abbildung 3: Browseransicht über ein Smartphone

Mit Express implementieren wir zuerst einen Webserver der auf Port 3000 hört. Dann setzen wir unseren Socket Server auf, der unter dem erstellen Webserver mitläuft. Wenn ein Client sich über Socket.IO mit dem connection Event verbindet geben wir eine debugging Meldung auf der Konsole aus. Das gleich für das disconnect Event wenn ein Client die Verbindung beendet.

server.js
var server = require('http').Server(app);
var io = require('socket.io')(server);

server.listen(app.get('port'), app.get('ip'), function () {
    console.log('Express server listening on ' + app.get('ip') + ':' + app.get('port'));
});

io.on('connection', function (socket) {
    console.log('a user connected');
    socket.on('disconnect', function () {
        console.log('user disconnected');
    });
});

Über Socket.IO senden wir alle 50 Millisekunden den Wert der aktuellen CPU-Auslastung an die Clients die das cpuUtilization Event abonniert haben.

server.js
setInterval(function () {
    io.emit('cpuUtilization', cpu.getUtilization());
}, 50);

Um das Dependency Injection System von AngularJS nutzen zu können definiere ich einen Service mit dem Namen websocket und binden so das socket Objekt von der Socket.IO Bibliothek ein. In diesem Service baue ich eine Websocket Verbindung zum Server auf. Jeder socket Callback wird in ein $scope.$apply verpackt. Dadurch überprüft AngularJS nachdem der übergebene Callback aufgerufen wurde, ob sich der Status geändert hat und aktualisiert die View wenn erforderlich.

public/app/modules/cpu/cpuFactory.js
angular.module('cpu').factory('websocket', ['$rootScope', function ($rootScope) {
	var socket = io.connect();

	return {
		on: function (event, callback) {
			socket.on(event, function () {
				var args = arguments;
				$rootScope.$apply(function () {
					callback.apply(socket, args);
				});
			});
		}
	}
}]);

Im Controller setzten wir eine Callbackfunktion die aufgerufen wird, wenn über Socket.IO ein neues cpuUtilization Event eintrifft. Die Callbackfunktion fügt den neuen Wert in den Graphen ein und zeichnet den Graphen neu damit die Änderung im Browser sichtbar wird.

public/app/modules/cpu/cpuController.js
    websocket.on('cpuUtilization', function (state) {
        $scope.graph.series.addData({ cpu: parseInt(state) });
        $scope.graph.render();
    });

Abschließend eine kurze Übersicht der Anwendung. Die Anwendung besitzt eine statische Navigations- und Fußleiste die immer gleich bleiben. Dazwischen werden die einzelnen Seiten von AngularJS gerendert.

Abbildung 4: Unterteilung in Module und Komponenten

Navigiert der Benutzer auf die CPU Seite so wird der CpuCtrl (Controller) aktiv und rendert mit Hilfe des Templates den Seiteninhalt.

public/app/modules/cpu/cpu.html

<div id='cpu' class='container content_container' role='main' ng-controller='CpuCtrl'>

<div class='panel panel-default'>

<div class='panel-heading'>

<h3 class='panel-title'>
               <cpu-info></cpu-info>
            </h3>

        </div>


<div class='panel-body'>
            <cpu-graph renderer='area' color='#ff0000'></cpu-graph>
        </div>

    </div>

</div>

Das Template wiederum verwendet die von mir erstellten cpu-info und cpu-graph Direktiven.

public/app/components/cpu.js
angular.module('cpuComponent', [])
    .directive('cpuInfo', function () {
        return {
            restrict: 'E',
            template: '<b>Cpu Utilization:</b> {{state}}%'
        }
    })
    .directive('cpuGraph', function () {
        return {
            restrict: 'E',
            template: '
<div id="chart"></div>

',
            link: function (scope, el, attrs) {
                scope.graph = new Rickshaw.Graph({
                    element: document.querySelector('#chart'),
                    width: null,
                    height: 300,
                    renderer: attrs.renderer,
                    min: 0,
                    max: 100,
                    series: new Rickshaw.Series.FixedDuration([{ name: 'CPU', color: attrs.color }], undefined, {
                        timeInterval: 250,
                        maxDataPoints: 500,
                        timeBase: new Date().getTime() / 1000
                    })
                });

                scope.graph.render();
            }
        }
    });

Der Quellcode kann über Github bezogen werden. Der Quellcode im master Branch ist mit vielen Kommentaren zur ausführlichen Erläuterung der Applikation versehen. Eine Variante ohne Kommentar befindet sich im no_comment Branch.

1.   https://nodejs.org/en/

2.   https://www.tttech.com/de/markets/automotive/projekte-referenzen/ttx-datalogger-mit-audi/ 

3.   https://www.npmjs.com/

4.   http://socket.io/

5.   http://expressjs.com/de/

6.   https://angularjs.org/

7.   http://karma-runner.github.io/1.0/index.html

8.   https://jasmine.github.io/1.3/introduction.html

9.   https://bower.io/

10. http://getbootstrap.com/

11.  http://code.shutterstock.com/rickshaw/


Autor: Tobias D. (Entwicklung Konzepte & Tooling)
Kontakt: techblog@bfft.de 
Bildquelle: Tobias D.

Themenvorschau Februar 2017: „High Voltage to C / Switched Current Source Precharge“ – Kurzschluss ade! – Wie man einen Kondensator verlustfrei mit Hochspannung lädt.


Jobs bei BFFT: