Angular & Electron App - daaren-urthling/tutorials GitHub Wiki

This tutorial show how to build an Electron desktop application using Angular. We will use the Typescript language not only in Angular components, but also in the Electron part, which is normally written in Javascript.

Everything is done with Visual Studio Code from the command line, using the Angular CLI.

Prerequisites

  • Visual Studio Code
  • Node.js
  • Angular

Create the Angular App

From a command-line window, scaffold a new Angular application using the Angular CLI:

ng new ng-electron

Open it as a VS Code folder:

code ng-electron

Create the Electron app

Note: for a full introduction about creating Electron apps, see Electron - Quick Start

Open a VS Code terminal window and install the Electron package by running:

npm install --save-dev electron

As we will develop our Angular app in Typescript, we want to do the same for the Electron back-end. Create then the src\electron-main.ts file with this content:

import { app, BrowserWindow } from "electron";
import * as path from "path";
import * as url from "url";

let mainWindow: Electron.BrowserWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        height: 600,
        width: 800,
    });
    mainWindow.loadURL(url.format({
        pathname: path.join(__dirname, ".", "ng-electron", "index.html"),
        protocol: 'file:',
        slashes: true
    }));
}
app.on("ready", createWindow);

Note: the name is chosen to not conflict with the main.ts file of the Angular app

Create a file named tsconfig.electron.json with the following content:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "module": "commonJS"
  },
  "files" : [
    "src/electron-main.ts"
  ]
}

This will be used to compile all the Typescript sources we will use in the Electron part of the application.
Bascically we are using the same settings used by Angular, with some minor differences.
As soon as we will need to add mor .ts files for our Electron part, we will list there in the files section.

Finally, edit the package.json file and change the following lines:

{
...
  "main": "dist/electron-main.js",
  "scripts": {
     "start": "ng build --base-href ./ && tsc -p tsconfig.electron.json && electron ."
   }
...
}

This way, the npm start command will build the Angular app, compile the Typescript Electron modules and then start the Electron runtime open the application.

You can then try your app by running:

npm start

from your application's directory.

Angular / Electron interaction

In this moment, Electron is just a host for our Angular application.
The real interesting thing in building an Eelctron application is to leverage all the possibilities offered by Node.js ecosystem while having a cool UI written with modern web technologies.

To better interact with Electron from Angular, we will use Ngx-electron, a small module for Angular which makes calling Electron APIs from the Renderer Process easier.
Install it with the following command:

npm i ngx-electron --save

Add the installed module to those loaded by the Angular engine, so this it will be available later. Change the app.module.ts file in this way:

...
import { NgxElectronModule } from 'ngx-electron';
...
imports: [
    ...
    NgxElectronModule,
  ],
...

The Electron Architecture use two separate processes to run the main script and the UI (using the Chromium library).
The best way to communicate between these two processes (and then having the Angular interface make use of some Node.js module) is via event messaging sent between the ipc_renderer and ipc_main processes, two elements available in the Electron environment.

To setup this communication, add this code to the electron-main.ts file:

import { ..., ipcMain } from "electron";
...
function setEventHandlers() {
    ipcMain.on('action', (event, argument) => {
        event.sender.send('action-succeeded', 'Doing ...');
        setTimeout(() => {
            event.sender.send('action-succeeded', '...done');
        }, 3000)
    });
}
...
app.on("ready", () => {
    createWindow();
    setEventHandlers();
});

Here we are handling an event that will arrive from the Angular UI simulating a time-consuming action via a setTimeout call, and then responding upon completion.

On the other hand, we will send an event from the Angular UI and receive the response.
Change the content of you app-component.html with this:

<button (click)="onAction()">Action</button>
<p>{{result}}</p>

Integrate the app.component.ts file in this way:

import { ..., ChangeDetectorRef } from '@angular/core';
import { ElectronService } from 'ngx-electron';
...
export class AppComponent {
...
  result: string = '';
...
  constructor(
    private electron: ElectronService,
    private chd: ChangeDetectorRef
  ) {
    this.electron.ipcRenderer.on('action-succeeded', (event, arg) => {
      this.result = this.result + '\n' + arg;
      this.chd.detectChanges();
    })
  }

  onAction() {
    this.electron.ipcRenderer.send('action', true);
  }  

We need to explicitly handle the change detection here, as the events coming from the Electron main process are not handled automatically by Angular as other events (i.e., user interacting with the page).
If we don't do this way, the changes of the data model would not reflect on the UI.

We can test now our app. Pressing the "Action" button should display a "Doing ..." message followed by a "... done" after 3 seconds.

Debugging our application

Going on, it will be useful to debug our code as soon as we will add some application logic.

To debug the Angular part, you can open a Chrome debugger in the Electron shell by selecting View | Toggle Developer Tools.

To debug the Electron part, you can use the VS Code debugger, by adding the following debug configuration (Debug | Open Configurations from the VS Code menu):

{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Debug Main Process",
        "type": "node",
        "request": "launch",
        "cwd": "${workspaceRoot}",
        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
        "windows": {
          "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
        },
        "args" : ["."]
      }
    ]
  }

Pressing F5 the application will be started and the debugger attached; it is possibile to set the breakpoints directly in the Electron .ts files, as the source map has been generated for them.

Hot reload of the Angular application is not supported, however it is possible to open a second VS Code Terminal window and rebuild the Angular part (ng build --base-href ./) without closing the Electron application.
It is sufficent, then, reload the page in the Electron shell using View | Force Reload to see the changes in the code.

For a better development cycle it can be useful to set the following instructions in the package.json file:

"scripts": {
    "ng": "ng build --base-href ./",
    "ts": "tsc -p tsconfig.electron.json",
    "build": "npm run ng && npm run ts",
    "start": "npm run build && electron ."
  },

and have available the following commands:

  • npm run ng to build just the Angular part
  • npm run ts to build just the Electron part
  • npm run build to build all; then, i.e., press F5 to start the debugger.
  • npm start to build all and start the application
⚠️ **GitHub.com Fallback** ⚠️