In this article, we’ll learn how to build cross-platform desktop apps for Windows, Linux and macOS using Electron and web technologies such as HTML, TypeScript and Angular.

Electron was initially developped for GitHub’s Atom editor. Nowadays, it’s being used by big companies like Microsoft and Slack to power their desktop apps. Visual Studio Code is a powerful and popular code editor built by Microsoft using Electron.

You can check out more apps built with Electron from this link.

Table of Contents

  • What Electron Precisely Does?
  • Prerequisites
  • Installing Angular CLI 8
  • Creating a Project
  • Installing & Setting up Electron
  • Calling Electron APIs by Example
  • Packaging your Electron App
  • Conclusion

What Electron Precisely Does?

So you are a frontend web developer - you know JavaScript, HTML and CSS which is great but you need to build a desktop application. Thanks to Electron that’s now possible and you don’t have to learn classic programming languages like C++ or Java to build your application, you can simply use your web development skills to target all the popular desktop platforms such as macOS, Linux and Windows with one code base. You only need to rebuild your code for each target platform.

Electron simply provides a native container for your web application so it looks and feels like a desktop application. If you are familiar with hybrid mobile development, Electron is quite similar to Apache Cordova but targets desktop systems instead of mobile operating systems.

Electron is actually an embedded web browser (Chromuim) bundled with Node.js and a set of APIs for interfacing with the underlying operating system and providing the services that are commonly needed by native desktop apps such as:

Let’s now see how we can use Electron and web technologies (TypeScript and Angular) to create a desktop app.

Prerequisites

In this tutorial, we’ll build a simple desktop application from scratch, so you are going to need the following prerequisistes:

  • Familiarity with the three pillars of the web i.e JavaScript, CSS and HTML.
  • A working knowledge of Angular as we’ll be using it to build our application along with Electron.
  • Git, Node.js and NPM installed on your system.

Installing Angular CLI 8

We’ll be using Angular for creating our web application that will be wrapped by Electron. The Angular team provides a command line interface that can be used to quickly scaffold Angular projects and work with them locally.

Angular CLI is based on Node, so provided that you have Node and NPM installed on your machine, you can simply run the following command to install the CLI:

$ npm install -g @angular/cli

As the time of this writing, Angular CLI v8.1.0 will be installed on your system.

Creating a Project

Let’s now create a project. In your terminal, run the following command:

$ ng new electron-app

The CLI will prompt you if Would you like to add Angular routing? (y/N) type y if you want to add routing and N otherwise. You will also be asked for Which stylesheet format would you like to use? Let’s keep it simple and choose CSS.

That’s it, the CLI will create a folder structure with the necessary files for configuring and bootsrapping your project and start installing the dependencies from npm.

You can make sure your application works as expected by serving it locally using the following commands:

$ cd electron-app
$ ng serve

You can see your app up and running by going to the http://localhost:4200/ address in your web browser. You should see the following interface:

Let’s now see how we can turn this simple web app into a desktop application using Electron.

Installing & Setting up Electron

After creating our Angular project, let’s now install Electron using the following commands:

$ npm install --save-dev electron@latest

This will install Electron as a development dependncy in your project.

As of this writing, electron v5.0.6 is installed.

Next, create an app.js file inside the root folder of your project and add the following content:

const {app, BrowserWindow} = require('electron')
    const url = require("url");
    const path = require("path");

    let mainWindow

    function createWindow () {
      mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true
        }
      })

      mainWindow.loadURL(
        url.format({
          pathname: path.join(__dirname, `/dist/electron-app/index.html`),
          protocol: "file:",
          slashes: true
        })
      );
      // Open the DevTools.
      mainWindow.webContents.openDevTools()

      mainWindow.on('closed', function () {
        mainWindow = null
      })
    }

    app.on('ready', createWindow)

    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit()
    })

    app.on('activate', function () {
      if (mainWindow === null) createWindow()
    })

We simply create a GUI window using the BrowserWindow API provided by Electron and we call the loadURL() method to load the index.html file from the dist folder.

Note: At this point, if you look at your project’s folder you will not find a dist folder because we didin’t build our Angular project yet.

According to the docs, BrowserWindow can be used to create and control browser windows. You can use the backgroundColor property to set the background color. You can invoke BrowserWindow multiple times in your application to create multiple windows and use the parent property to add parent-child relationships between the various windows and also create modals.

Note: If you want to create a window without chrome, or a transparent window with an arbitrary shape, you need to use the Frameless Window API instead.

Next, you need to open the package.json file and add the app.js file as the main entry point of our project:

    {
      "name": "electron-app",
      "version": "0.0.0",
      "main": "app.js",
      // [...]
    }

You can find more information about the main key from the official docs.

Next, let’s modify the start script in the package.json file to make it easy to build the project and run the Electron application with one command:

    {
      "name": "electron-app",
      "version": "0.0.0",
      "main": "app.js",
      "scripts": {
        "ng": "ng",
        "start": "ng build --base-href ./ && electron .",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
      }, 
      // [...]
    }

Now, if we run the start script, our project will be built with base href set to ./ then our Electron app will run from the current folder.

Note: The base href attribute is used to set the base URL that will be used for the relative links in the document.

We can test our application by running the following command:

$ npm start

The Angular project will be built inside the dist/electron-app folder and Electron will be started with a GUI window that should display the Angular application.

Since we are using Angular 8, the latest version as of this writing, which makes use of differential loading by default - A feature that enables the web browser to make a choice between loading modern (ES6+) or legacy JavaScript (ES5) source files based on its own capabilities. After compiling the application, we’ll have two builds, a modern build (es2015) and a legacy build (es5).

If you look at the console of your Electron application, you’ll see that it tries to load the modern bundles which gives us the Failed to load module script error.

This is a screenshot of our application with the error displayed in the console:

One way to solve this issue is by instrcuting the TypeScript compiler to generate only a legacy ES5 build. Open the tsconfig.json file and change target from es2015 to es5:

{
  "compilerOptions": {
    "target": "es5",
}

Now, restart your application, you should see your Angular app loaded inside Electron. This is a screenshot:

Calling Electron APIs by Example

Let’s now see how we can call Electron APIs to invoke the services from the underlying system. We’ll see how we can take screenshots of our desktop windows using this example.

Electron use two processes - The main process which runs the Node.js runtime and a renderer process that runs the Chromium browser. For most APIs, we need to use inter-process communication to call the Electron APIs from the renderer process so we’ll make use of ngx-electron - A simple Angular wrapper for electron’s Renderer API which makes calling Electron APIs from the renderer process much easier.

Note: ngx-electron is a third-party package that makes calling Electron APIs from Angular more straightforward but you can also use the ipcMain module for implementing asynchronous communication from the main process to renderer processes.

According to the docs:

The ipcMain module is an instance of the EventEmitter class. When used in the main process, it handles asynchronous and synchronous messages sent from a renderer process (web page). Messages sent from a renderer will be emitted to this module.

Electron provides many useful APIs. Among them, desktopCapturer which according to the cocs allows us to access information about media sources that can be used to capture audio and video from the desktop using the navigator.mediaDevices.getUserMedia API.

In your terminal, run the following command:

$ npm install ngx-electron --save

Next, open the src/app/app.module.ts file and add NgxElectronModule to the imports array:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgxElectronModule } from 'ngx-electron';
// [...]

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgxElectronModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Next, you can import and inject ElectronService in your component(s) to call Electron APIs.

Open the src/app/app.component.ts file and import ElectronService and inject it via the component constructor:

import { ElectronService } from 'ngx-electron';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  title = 'electron-app';
  constructor(private _electronService: ElectronService) { }
}

Next, let’s define the following variables:

export class AppComponent implements OnInit{
  sources = [];
  selectedSource;
  videostream;
  @ViewChild('videoElement', { static: true }) videoElement: any;  
  video: any;

We’ll use:

  • the sources array to store the sources returned from the desktopCapturer API,
  • the selectedSource variable to store a source that we want to take a screenshot for,
  • the videostream variable to store the video steram from the webkitGetUserMedia() API,
  • the videoElement variable decorated with @ViewChild to create a query configuration that will be used to query for the <video> tag.
  • the video variable to store the native DOM element for the <video> tag.

#electron #typescript #angular #javascript #web-development

How to Build Desktop Apps with Electron, TypeScript and Angular
91.35 GEEK