Enwicklung von Desktop-Apps mit Angular und Electron

Enwicklung von Desktop-Apps mit Angular und Electron

In den letzten Jahren erfuhr die Entwicklung von Webapplikationen mehr und mehr an Bedeutung. Dies liegt vor allem daran, dass sich Anwendungen unabhängig vom Betriebssystem über den Browser nutzen lassen und mehr und mehr Daten und Services in die Cloud wandern.
Der Browser stellt in diesem Fall eine Art zusätzliche Abstraktionsebene dar und sorgt für den plattformübergreifenden Zugriff.
Oft werden trotz allem zusätzlich Desktop-Applikationen gewünscht.

Mit Hilfe des Electron Frameworks [1] lasen sich Webapps zu nativen Anwendungen für verschiedenste Systeme wandeln. So können wir beispielsweise macOS, Windows oder Linux-Applikationen erstellen und müssen nur wenige Codezeilen zu unserem bestehenden Code hinzufügen.
Electron nutzt zu diesem Zweck Node.js [2] und Chromium [3].
Im Prinzip wird eine Instanz des Chromium-Browsers erstellt, der lediglich die Webapplikation darstellt.

In diesem Artikel will ich Ihnen zeigen wie Sie mit Hilfe des Angular Frameworks eine simple Single-Page Applikation erstellen und diese anschließend mit Hilfe des Electron Frameworks als Desktop-Applikation ausliefern.

Installieren der benötigten Frameworks und Generatoren

Voraussetzung für die Durchführung ist die Installation von Node.js und NPM [2].
Anschließend benötigen wir zum schnellen Setup der Webapplikation die Angular-CLI. Diese wird als globales NPM-Modul wie folgt installiert:

npm install -g @angular/cli

Ebenso wollen wir im weiteren Verlauf Electron unter TypeScript verwenden. Dafür benötigen wir noch den TypeScript Compiler (tsc). Um die entsprechenden Angaben zur Typisierung und somit auch Hilfe von unserem Editor zu erhalten, installieren wir noch das typings-Package.
Darüber können wir später die entsprechenden Typings für Electron installieren.

npm install -g tsc typings

Anlegen der Angular App

Im nächsten Schritt erstellen wir die App. Der Nachfolgende Befehl legt einen neuen Ordner ng-electron-example an, erstellt das Grundgerüst für die App (der Präfix für Komponenten lautet in diesem Fall elex) und installiert alle benötigten Abhängigkeiten.

ng new ng-electron-example -p elex
cd ng-electron-example

Wenn wir nun npm start auf der Konsole ausführen und im Browser die URL http://localhost:4200 aufrufen, sollten wir sehen dass die App bereits gestartet wurde und der Text „elex works!“ angezeigt wird.

Installieren der benötigten Frameworks und Tools für die Desktop-Applikation

Um im nächsten Schritt aus der bestehenden Webapp eine Desktopanwendung zu erstellen, benötigen wir ein paar weitere Abhängigkeiten und Hilfsmittel.

Wir installieren diese über den Aufruf von NPM auf der Konsole:

npm install --save-dev copyfiles electron electron-packager rimraf typings

Nachfolgend ein paar kurze Erläuterungen zu den einzelnen Paketen:

Paket Verwendung
copyfiles Zum Kopieren von Dateien zwischen Ordnern
electron Das Electron Framework
electron-packager Ein Tool zum Erzeugen von systemspezifischen Electron Programmpaketen
rimraf Tool zum Löschen von Dateien und Ordnern
typings Ein Manager zur Installation von Typings

Konfiguration von Electron

An dieser Stelle möchte ich Ihnen zeigen, wie Sie das Electron Framework mit TypeScript verwenden.
Dazu legen wir zunächst ein neues Verzeichnis mit dem Namen electron an und öffnen es auf der Konsole:

mkdir electron && cd electron

Im nächsten Schritt wollen wir die von uns benötigten Typings finden. Dazu geben wir auf der Konsole den folgenden Befehl ein:

typings search electron

Uns wird nun eine Liste mit gefunden Typing-Einträgen angezeigt. Wir benötigen die Typings für electron und können diese nun wie folgt installieren:

typings install registry:dt/electron --save --global

Im aktuellen Verzeichnis wird nun ein Ordner typings erstellt, der die entsprechenden Angaben zur Typisierung beinhaltet.
Weiterhin wird die Datei typings.json angelegt, die Informationen zu den aktuell installieren Typings enthält:

{
  "globalDependencies": {
    "electron": "registry:dt/electron#1.4.8+20170316233216"
  }
}

Als nächstes benötigen wir die Konfigurationsdatei für TypeScript mit Angaben für den TypeScript Compiler. Dafür legen wir die Datei tsconfig.json im Verzeichnis electron mit dem folgenden Inhalt an:

{
  "compilerOptions": {
    "target": "es5",
    "outDir": "../dist",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "suppressImplicitAnyIndexErrors": true
  },
  "exclude": [
    "node_modules"
  ]
}

In der Konfiguration haben wir angegeben, dass der TypeScript Compiler uns JavaScript im es5-Standard erzeugen soll, da dieser in fast allen Browsern vollständig implementiert ist. Weiterhin geben wir als Ausgabeverzeichnis für den JavaScript-Code das dist-Verzeichnis an, dass von Angular erzeugt wird, sofern wir die App für den Produktiveinsatz bauen (mehr dazu später).
Weitere Details zu den einzelnen Konfigurationseinstellungen können der offiziellen TypeScript-Dokumentation entnommen werden [4].

Die wichtigsten Vorbereitungen haben wir nun getroffen. Jetzt wollen wir uns dem Quellcode zum Erstellen der Desktop-App zuwenden.
Wir legen dazu im electron-Ordner die Datei electron.js an.
Der benötigte Quellcode für die Erzeugung sieht wie folgt aus:

import { app, BrowserWindow } from 'electron';

class ElectronApp {
  static app: Electron.App;
  static BrowserWindow;
  static mainWindow: Electron.BrowserWindow;

  static main(app: Electron.App, browserWindow: typeof BrowserWindow) {
    ElectronApp.app = app;
    ElectronApp.BrowserWindow = browserWindow;
    ElectronApp.app.on('ready', ElectronApp.onReady);
    ElectronApp.app.on('window-all-closed', ElectronApp.onWindowAllClosed);
    ElectronApp.app.on('activate', ElectronApp.onActivate);
  }

  // call when ready
  private static onReady() {
    // Create the window
    ElectronApp.mainWindow = new BrowserWindow({
      width: 1200,
      height: 800,
      resizable: false,
      titleBarStyle: 'hidden',
      title: 'Angular Electron Example App',
      icon: `${__dirname}/favicon.ico`
    });

    // load index.html
    ElectronApp.mainWindow.loadURL(`file://${__dirname}/index.html`);

    // close the window
    ElectronApp.mainWindow.on('closed', ElectronApp.onClose);
  }

  // Quit after all windows has been closed
  private static onWindowAllClosed() {
    // keep app in application bar on OSX (because it's common)
    if (process.platform !== 'darwin') { ElectronApp.app.quit(); }
  }

  // fires if the window is closed
  private static onClose() {
    // Reset the window object to null
    ElectronApp.mainWindow = null;
  }

  // recreate if activated again
  private static onActivate() {
    if (ElectronApp.mainWindow === null) { ElectronApp.onReady(); }
  }

}

ElectronApp.main(app, BrowserWindow);

Wir rufen die statische main-Methode der Klasse ElectronApp auf und übergeben ihr die beiden imports app und BrowserWindow, die uns Electron bereitstellt. Weiterhin reagieren wir auf eintreffende Events mit den Methoden onReady(), onWindowAllClosed()
bzw. onActivate.
Die onReady-Methode erzeugt zunächst ein neues Fester. Dabei legen wir beispielsweise die Größe, sowie den Titel und ein Icon fest.
Ein Liste aller verfügbaren Optionen kann der offiziellen Dokumentation entnommen werden [5].
Über die Angabe in ElectronApp.mainWindow.loadURL teilen wir der Electron-App den Pfad zur Einstiegsseite mit.
Der letzte Aufruf in der onReady()-Methode wird abgearbeitet wenn das Fenster geschlossen wird.
Die onClose()-Methode überschreibt mainWindow mit dem Wert null.

In der onAllClosed()-Methode geben wir an, was passieren soll, wenn alle Fenster geschlossen werden (in unserem Fall existier nur eines).
Wir prüfen an dieser Stelle ob es sich bei dem System um ein macOS-System handelt und rufen in diesem Fall die ElectronApp.app.quit() auf.
Dies ist eine bekannte Vorgehensweise bei macOS, da ein Schließen der Anwendung diese immer noch in der Application Bar behält. Somit kann diese wieder reaktiviert werden. Dies geschieht in der onActivate()-Methode.

Jetzt wollen wir noch das Template anpassen und den von Electron bereitgestellten Webviewer einbauen:
Dafür passen wir die Datei index.html wie folgt an:

<!doctype html>
<html>
<!-- ... -->
<body>
  <elex-root>Loading...</elex-root>
  <webview src="https://d-koppenhagen.de/blog" autosize style="display:inline-flex; height:705px; width: 1190px;"></webview>
</body>
</html>

Geschafft! unsere App ist nun soweit vorbereitet. Nun müssen wir noch Angular und Electron zusammenführen.

Das Verheiraten von Angular und Electron

Wir haben nun alle notwendigen Vorbereitungen getroffen um eine Native Desktop-App mit Angular und Electron zu erstellen. Jetzt wollen wir dafür sorgen, dass die App erzeugt und für verschiedene Betriebssysteme bereitgestellt wird.
Dafür passen wir die Datei package.json an und erweitern diese um Scriptangaben und einer Angabe zur Hauptdatei von Electron.

// package.json
{
  // ...
  "scripts": {
    // ...
    "start-el": "npm run build && electron dist",
    "build": "ng build --prod --base-href=. && copyfiles package.json dist && cd electron && tsc",
    "deploy": "rimraf app && npm run build && electron-packager dist --all --electron-version=1.6.2 --overwrite --out=app && rimraf dist",
    "deploy-mac": "rimraf app/*mas* && npm run build && electron-packager dist --platform=mas --arch=all --electron-version=1.6.2 --overwrite --out=app --icon src/assets/icon.icns && rimraf dist",
    "deploy-win": "rimraf app/*windows* && npm run build && electron-packager dist --platform=win32 --arch=all --electron-version=1.6.2 --overwrite --out=app --icon src/assets/icon.ico && rimraf dist",
    "deploy-lin": "rimraf app/*linux* && npm run build && electron-packager dist --platform=linux --arch=all --electron-version=1.6.2 --overwrite --out=app --icon src/assets/icon.ico && rimraf dist",
    // ...
  },
  "main": "electron.js",
  // ...
}

Das Script build erzeugt zunächst die Angular-App in der Produktivversion. Wichtig dabei ist die Angabe der Option --base-href=., damit nach der Erzeugung alle Dateien gefunden werden können. Im nächsten Schritt wird die NPM-Paketkonfigurationsdatei in das dist-Verzeichnis kopiert. Dies ist notwendig, da wir hier nach den Scriptangaben die Startdatei für electron festlegen ("main": "electron.js"). Anschließend wird in das Verzeichnis electron navigiert und der TypeScript Compiler gestartet (tsc).

Das Script start-el ruft nun mittels npm run build das zuvor definierte build-Script auf und startet anschließend electron mit dem Inhalt des dist-Ordners.

Wir können jetzt unsere fertige App betrachten:

Die letzten Scripte sorgen dafür, dass die App als fertiges Bundle für das jeweilige Betriebssystem erzeugt wird. Die icon-Angabe setzt ein Symbol für die entsprechende Startdatei (z.B. .exe unter Windows). Bei der Angabe für macOS ist zu beachten, dass wir ein Icon im icns-Format benötigen:

src/assets/icon.icns

Mit dem Schalter --out legen wir als Ausgabeverzeichnis den Ordner app fest. Über --platform legen wir die jeweilige Zielplattform fest und geben mit --arch die Architektur (z.B. x86 oder x64) an. die Angabe all sorgt dafür, dass die App für mehrere Plattformen bzw. Architekturformen erzeugt wird.

Da unser Verzeichnis unter Versionskontrolle steht sollten wir den App-Ordner noch davon ausschließen. Dazu passen wir die Datei .gitignore an und fügen die folgenden Zeilen hinzu:

# ...
# Deployed Apps
app/

Fertig! Sie haben nun gelernt wie Sie mit Hilfe von Angular und Electron Desktop-Applikationen für verschiedenste Betriebssysteme erzeugen können. Selbstverständlich war dies nur eine kurze Einführung uns Sie können noch viel mehr Funktionalitäten von Angular und Electron nutzen.

Der vollständige Quellcode kann auch auf Github eingesehen werden.

Sind Sie noch unerfahren im Umgang mit Angular? Dann schauen Sie sich doch auch das Einsteigerbuch zu Angular an oder kontaktieren mich für eine persönliche Beratung.

Viel Spaß beim programmieren.

Teilen

Über den Autor

Danny Koppenhagen

Danny Koppenhagen administrator

Softwareentwickler und IT-Berater.

Kommentare