Last week, I took part in the Google Developer Day held in Beijing. The Angular team introduced their new Angular 2. Angular is a development platform for building mobile and desktop web applications.

This tutorial shows how to configure and use Angular 2 web components with the Electron framework for creating native cross-platform applications with web technologies.

As recommended by the Angular team, TypeScript will be used throughout this tutorial. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open-source.

You will get a link to the finished working example on GitHub at the end of the article.

Prerequisites

Before starting, please make sure that node, npm, typescript, typings are installed.

npm install -g typescript typings

Setup Electron with TypeScript

The first thing you want to do is to initialize your project using package.json:

{
    "name": "App-Name",
    "version": "1.0.0",
    "main": "main.ts",
    "scripts": {
        "start": "tsc main.ts && electron main.js",
        "clean": "rm -Rf *.log *.js",
        "tsc:w": "tsc -w",
        "typings": "typings",
        "postinstall": "typings install"
    },
    "author": "Your Name",
    "license": "GPL-2.0",
    "devDependencies": {
        "typescript": "~2.1.4",
        "typings": "^2.0.0"
    },
    "dependencies": {
        "electron": "^1.4.12"
    }
}

Typings

The type definition files used by this project are managed by the “typings” TypeScript Definition Manager, version 1.0 or higher. It is not necessary to have “typings” installed just to run this application. If you haven’t installed this, you’ll need to install “typings” as a global NPM module:

npm install -g typings

The type definitions are committed to source control, as the `typings.json` file and the `typings` subdirectory. To get the latest type definitions, delete that file and subdirectory and replace them by running the following commands:

typings install dt~electron/github-electron --save --global
typings install dt~node --save --global

After that, a directory named `typings` and a file named `typings.json` are created.

TypeScript

Next, you’ll need to set up TypeScript. For this, you’ll need a tsconfig.json file in your root. I’ll just give you the file you’ll need here:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "removeComments": false,
        "noImplicitAny": false
    },
    "exclude": [
        "node_modules",
        "typings"
    ]
}

Please note the exclude option. This setting greatly improves performance when using Atom or IntelliJ for development.

The Electron Shell with TypeScript

Now comes the fun part. The normal code for creating the shell for an Electron application in JavaScript normally looks something like this:

const electron = require('electron');
// Module to control application life.
const app = electron.app; 
 
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow; 
 
// Keep a global reference of the window object,
// if you don't, the window will be closed automatically
// when the JavaScript object is garbage collected.
var mainWindow = null;
// Quit when all windows are closed.
app.on('window-all-closed', function() {
    // On OS X it is common for applications and their
    // menu barto stay active until the user quits
    // explicitly with Cmd + Q
    if (process.platform != 'darwin') {
        app.quit();
    }
});
 
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function() {
    // Create the browser window.
    mainWindow = new BrowserWindow({width: 800, height: 600});
 
    // and load the index.html of the app.
    mainWindow.loadURL('file://' + __dirname + '/index.html');
 
    // Open the DevTools.
    mainWindow.webContents.openDevTools();
 
    // Emitted when the window is closed.
    mainWindow.on('closed', function() {
        // Dereference the window object, usually you
        // would store windows in an array if your
        // app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
    });
});

The trick we want to perform is to create a TypeScript class that does essentially the same thing but uses TypeScript syntax.

/// <reference path="typings/index.d.ts" />
import electron = require("electron");
let app = electron.app;
let BrowserWindow = electron.BrowserWindow;
 
// Global reference to the main window, so the garbage collector doesn't close it.
let mainWindow : Electron.BrowserWindow;
 
// Opens the main window, with a native menu bar.
function createWindow() {
    // Create the browser window.
    mainWindow = new BrowserWindow({width: 800, height: 600});
 
    // and load the index.html of the app.
    mainWindow.loadURL(`file://${__dirname}/index.html`);
 
    // Open the DevTools.
    // mainWindow.webContents.openDevTools();
 
    // Emitted when the window is closed.
    mainWindow.on("closed", () => {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
    });
}
 
// Call 'createWindow()' on startup.
app.on("ready", () => {
    createWindow();
});
 
// On OS X it is common for applications and their menu bar to stay active until the user quits explicitly
// with Cmd + Q.
app.on("window-all-closed", () => {
    if (process.platform !== "darwin") {
        app.quit()
    }
});
 
// On OS X it's common to re-create a window in the app when the dock icon is clicked and there are no other
// windows open.
app.on("activate", () => {
    if (mainWindow === null) {
        createWindow();
    }
});

Finally, created an HTML file named `index.html` containing the following contents:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
</head>
<body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using node <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
</body>
</html>

Running application

npm install && npm start

and you should see something like the following:

Setup Electron with Angular 2

First of all, you need to add dependencies of Angular to package.json:

{
    "name": "App-Name",
    "version": "1.0.0",
    "main": "main.ts",
    "scripts": {
        "start": "tsc && concurrently \"npm run tsc:w\" \"electron main.js\"",
        "clean": "rm -Rf *.log *.js *.map app/*.js app/*.map",
        "tsc:w": "tsc -w",
        "typings": "typings",
        "postinstall": "typings install"
    },
    "author": "Your Name",
    "license": "GPL-2.0",
    "devDependencies": {
        "concurrently": "^3.0.0",
        "electron-packager": "^8.4.0",
        "typescript": "~2.1.4",
        "typings": "^2.0.0"
    },
    "dependencies": {
        "@angular/common": "^2.3.0",
        "@angular/compiler": "^2.3.0",
        "@angular/core": "^2.3.0",
        "@angular/http": "^2.3.0",
        "@angular/material": "2.0.0-alpha.11-3",
        "@angular/platform-browser": "^2.3.0",
        "@angular/platform-browser-dynamic": "^2.3.0",
        "electron": "^1.4.12",
        "reflect-metadata": "^0.1.8",
        "rxjs": "5.0.0-rc.4",
        "systemjs": "^0.19.41",
        "zone.js": "^0.7.2"
    }
}

You will need to install new libraries once updating your package.json file:

npm install

Then, you need to install another two typings for Angular:

typings install dt~core-js --save --global
typings install dt~jasmine --save --global

Next, create a file named system.config.ts:

/** Type declaration for ambient System. */
declare var System: any;
 
/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
System.config({
    paths: {
        // paths serve as alias
        'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
        // our app is within the app folder
        app: 'app',
        // angular bundles
        '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
        '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
        '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
        '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
        '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
        '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
        // other libraries
        'rxjs': 'npm:rxjs',
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
        app: {
            main: './main.js',
            defaultExtension: 'js'
        },
        '@angular/material': {
            format: 'cjs',
            main: 'npm:@angular/material/material.umd.js'
        },
        rxjs: {
            defaultExtension: 'js'
        }
    }
});

Modify index.html and include javascript for Angular:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Angular 2 with Electron</title>
    <!-- 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script type="text/javascript" src="node_modules/core-js/client/shim.min.js"></script>
    <script type="text/javascript" src="node_modules/zone.js/dist/zone.js"></script>
    <script type="text/javascript" src="node_modules/reflect-metadata/Reflect.js"></script>
    <script type="text/javascript" src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script type="text/javascript" src="systemjs.config.js"></script>
    <script type="text/javascript">
        System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <my-app>Loading...</my-app>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using node <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
</body>
</html>

Simple Angular 2 application

This step does not differ from Step 2: Our first Angular component chapter of the 5 min quickstart. Please check the original article for more details on implementation.

Create an app subfolder in the project root directory to hold the following files:

app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';
 
@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
app/app.component.ts
import { Component } from '@angular/core';
 
@Component({
  selector: 'my-app',
  template: '<h1>My First Angular 2 App with Electron</h1>'
})
export class AppComponent { }
app/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule }              from './app.module';
 
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Running application

npm start

The start command compiles your TypeScript code and launches Electron instance with your application getting loaded automatically.

Also, note that the start command runs the TypeScript compiler in watch mode. Every time you change your code it should be enough just reloading Electron via “View ⟶ Reload” or with Cmd-R (Ctrl-R on Windows).

FAQ

Question: How to deal with the error message: “Duplicate identifier ‘PropertyKey’”?
Answer:
Try the following solutions:

  1. remove "@types/core-js": "0.9.34", from devDependencies in package.json.
  2. Add the following lines to tsconfig.json:
"exclude": [
    "node_modules",
    "typings"
]

Source Code

The source code for this tutorial is open-sourced on GitLab.

References

The Disqus comment system is loading ...
If the message does not appear, please check your Disqus configuration.