- Add Electron to an Angular application
- Add Electron and other relevant dependencies
- Add Electron build configuration
- Create the necessary typescript configurations
- Modify angular.json
- Add Angular Electron directives
- Add access Electron APIs
- Create the electron window in
main.ts
- Add the electron window and improve the
package.json
scripts
This cookbook recipe explains how to integrate Electron in an Angular 6+ application. Electron is a framework for creating native applications with web technologies like JavaScript, HTML, and CSS. As an example, very well known applications as Visual Studio Code, Atom, Slack or Skype (and many more) are using Electron too.
ℹ️
|
At the moment of this writing Angular 7.2.3 and Electron 4.0.2 were the versions available. |
Here are the steps to achieve this goal. Follow them in order.
There are two different approaches to add the dependencies in the package.json
file:
-
Writing the dependencies directly in that file.
-
Installing using
npm install
oryarn add
.
❗
|
Please remember if the project has a package-lock.json or yarn.lock file use npm or yarn respectively.
|
In order to add the dependencies directly in the package.json
file, include the following lines in the devDependencies
section:
"devDependencies": {
...
"electron": "^4.0.2",
"electron-builder": "^20.38.5",
"electron-reload": "^1.4.0",
"npm-run-all": "^4.1.5",
"npx": "^10.2.0",
"wait-on": "^3.2.0",
"webdriver-manager": "^12.1.1"
...
},
As indicated above, instead of this npm install
can be used:
$ npm install -D electron electron-builder electron-reload npm-run-all npx wait-on webdriver-manager
Or with yarn
:
$ yarn add -D electron electron-builder electron-reload npm-run-all npx wait-on webdriver-manager
In order to configure electron builds properly a electron-builder.json
must be included in the root folder of the application. For more information and fine tuning please refer to the Electron Builder official documentation.
The contents of the file will be something similar to the following:
{
"productName": "app-name",
"directories": {
"output": "release/"
},
"files": [
"**/*",
"!**/*.ts",
"!*.code-workspace",
"!LICENSE.md",
"!package.json",
"!package-lock.json",
"!src/",
"!e2e/",
"!hooks/",
"!angular.json",
"!_config.yml",
"!karma.conf.js",
"!tsconfig.json",
"!tslint.json"
],
"win": {
"icon": "dist/assets/icons",
"target": ["portable"]
},
"mac": {
"icon": "dist/assets/icons",
"target": ["dmg"]
},
"linux": {
"icon": "dist/assets/icons",
"target": ["AppImage"]
}
}
Theres two important things in this file:
-
"output": this is where electron builder is going to build our application
-
"icon": in every OS possible theres an icon parameter, the route to the icon folder that will be created after building with angular needs to be used here. This will make it so the electron builder can find the icons and build.
In order to initiate electron in an angular app we need to modify the tsconfig.json
file and create a new one named tsconfig-serve.json
in the root folder.
This file needs to be modified to add the main.ts
and src/**/*
folders excluding the node_modules
:
{
....
},
"include": [
"main.ts",
"src/**/*"
],
"exclude": [
"node_modules"
]
....
}
In the root, tsconfig-serve.json
needs to be created. This typescript config file is going to be used when we serve electron:
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"es2016",
"es2015",
"dom"
]
},
"include": [
"main.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
angular.json
has to to be modified so the project is build inside /dist without an intermediate folder.
{
....
"architect": {
....
"build": {
outputPath": "dist",
....
}
In order to use Electron’s webview tag and its methods inside an Angular application our project needs the directive webview.directive.ts
file. We recommend to create this file inside a shared module folder, although it has to be declared inside the main module app.module.ts
.
import { Directive } from '@angular/core';
@Directive({
selector: '[webview]',
})
export class WebviewDirective {}
To call Electron APIs from the Renderer process, install ngx-electron module.
With npm
:
$ npm install ngx-electron --save
Or with yarn
:
$ yarn add ngx-electron --save
This package contains a module named NgxElectronModule which exposes Electron APIs through a service called ElectronService
As an example, the webview.directive.ts
file is located inside a shared
module:
// imports
import { NgxElectronModule } from 'ngx-electron';
import { WebviewDirective } from './shared/directives/webview.directive';
@NgModule({
declarations: [AppComponent, WebviewDirective],
imports: [
...
NgxElectronModule
...
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Here NgxElectronModule is also added so ElectronService can be injected wherever is needed.
After that is done, the use of hash has to be allowed so electron can reload content properly. On the app-routing.module.ts
:
....
imports: [RouterModule.forRoot(routes,
{
....
useHash: true,
},
)],
In order to use Electron in any component class the ElectronService must be injected:
import { ElectronService } from 'ngx-electron';
...
constructor(
// other injected services
public electronService: ElectronService,
) {
// previous code...
if (electronService.isElectronApp) {
// Do electron stuff
} else {
// Do other web stuff
}
}
💡
|
A list of all accesible APIs can be found at Thorsten Hans' ngx-electron repository. |
In order to use electron, a file needs to be created at the root of the application (main.ts
). This file will create a window with different settings checking if we are using --serve
as an argument:
import { app, BrowserWindow, screen } from 'electron';
import * as path from 'path';
import * as url from 'url';
let win: any;
let serve: any;
const args: any = process.argv.slice(1);
serve = args.some((val) => val === '--serve');
function createWindow(): void {
const electronScreen: any = screen;
const size: any = electronScreen.getPrimaryDisplay().workAreaSize;
// Create the browser window.
win = new BrowserWindow({
x: 0,
y: 0,
width: size.width,
height: size.height,
// Needed if you are using service workers
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
}
});
if (serve) {
// tslint:disable-next-line:no-require-imports
require('electron-reload')(__dirname, {
electron: require(`${__dirname}/node_modules/electron`),
});
win.loadURL('http://localhost:4200');
} else {
win.loadURL(
url.format({
pathname: path.join(__dirname, 'dist/index.html'),
protocol: 'file',
slashes: true,
}),
);
}
// Uncoment the following line if you want to open the DevTools by default
// win.webContents.openDevTools();
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
// tslint:disable-next-line:no-null-keyword
win = null;
});
}
try {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// 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.
if (win === null) {
createWindow();
}
});
} catch (e) {
// Catch Error
// throw e;
}
Inside package.json
the electron window that will be transformed to main.js
when building needs to be added.
{
....
"main": "main.js",
"scripts": {
....
}
The scripts
section in the package.json
can be improved to avoid running too verbose commands. As a very complete example we can take a look to the My Thai Star’s scripts
section and copy the lines useful in your project.
"scripts": {
"postinstall": "npx electron-builder install-app-deps",
".": "sh .angular-gui/.runner.sh",
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json -o",
"start:electron": "npm-run-all -p serve electron:serve",
"compodoc": "compodoc -p src/tsconfig.app.json -s",
"test": "ng test --browsers Chrome",
"test:ci": "ng test --browsers ChromeHeadless --watch=false",
"test:firefox": "ng test --browsers Firefox",
"test:ci:firefox": "ng test --browsers FirefoxHeadless --watch=false",
"test:firefox-dev": "ng test --browsers FirefoxDeveloper",
"test:ci:firefox-dev": "ng test --browsers FirefoxDeveloperHeadless --watch=false",
"test:electron": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"ngsw-config": "npx ngsw-config dist ngsw-config.json",
"ngsw-copy": "cp node_modules/@angular/service-worker/ngsw-worker.js dist/",
"serve": "ng serve",
"serve:open": "npm run start",
"serve:pwa": "npm run build:pwa && http-server dist -p 8080",
"serve:prod": "ng serve --open --prod",
"serve:prodcompose": "ng serve --open --configuration=prodcompose",
"serve:node": "ng serve --open --configuration=node",
"build": "ng build",
"build:pwa": "ng build --configuration=pwa --prod --build-optimizer && npm run ngsw-config && npm run ngsw-copy",
"build:prod": "ng build --prod --build-optimizer",
"build:prodcompose": "ng build --configuration=prodcompose ",
"build:electron": "npm run electron:serve-tsc && ng build --base-href ./",
"build:electron:dev": "npm run build:electron -- -c dev",
"build:electron:prod": "npm run build:electron -- -c production",
"electron:start": "npm-run-all -p serve electron:serve",
"electron:serve-tsc": "tsc -p tsconfig-serve.json",
"electron:serve": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve",
"electron:local": "npm run build:electron:prod && electron .",
"electron:linux": "npm run build:electron:prod && npx electron-builder build --linux",
"electron:windows": "npm run build:electron:prod && npx electron-builder build --windows",
"electron:mac": "npm run build:electron:prod && npx electron-builder build --mac"
},
Here the important thing to look out for is that the base href when building electron can be changed as needed. In our case:
"build:electron": "npm run postinstall:electron && npm run electron:serve-tsc && ng build --base-href \"\" ",
ℹ️
|
Some of these lines are intended to be shortcuts used in other scripts. Do not hesitate to modify them depending on your needs. |
Some usage examples:
$ npm run electron:start # Serve Angular app and run it inside electron
$ npm run electron:local # Serve Angular app for production and run it inside electron
$ npm run electron:windows # Build Angular app for production and package it for Windows OS
$ yarn run electron:start # Serve Angular app and run it inside electron
$ yarn run electron:local # Serve Angular app for production and run it inside electron
$ yarn run electron:windows # Build Angular app for production and package it for Windows OS