Angular and Electron: Building Desktop Apps

This tutorial will guide you through the process of building desktop applications using Angular and Electron. We will start by explaining what Angular and Electron are, and then move on to setting up the development environment. We will cover topics such as creating components, styling with Angular Material, handling user interactions, and integrating with Electron. Finally, we will discuss packaging and distributing the app, testing and debugging, and provide a conclusion.

angular electron building desktop apps

Introduction

What is Angular?

Angular is a popular open-source framework for building web applications. It allows developers to create dynamic and responsive single-page applications using HTML, CSS, and JavaScript/TypeScript. Angular provides a powerful set of tools and features, such as data binding, dependency injection, and modular architecture, that make it easier to develop complex applications.

What is Electron?

Electron is a framework that allows developers to build cross-platform desktop applications using web technologies. It combines the Chromium rendering engine and Node.js runtime, enabling developers to use HTML, CSS, and JavaScript to create desktop applications that can run on Windows, macOS, and Linux.

Benefits of using Angular and Electron together

By combining Angular and Electron, developers can leverage the power of both frameworks to build desktop applications with a rich user interface and seamless integration with the underlying operating system. Angular provides the tools for building responsive and dynamic user interfaces, while Electron enables the application to run as a standalone desktop app with access to native capabilities.

Setting Up the Development Environment

To get started with Angular and Electron, we need to set up the development environment. This involves installing Node.js and npm, creating a new Angular project, and adding Electron to the project.

Installing Node.js and npm

Before we can start using Angular and Electron, we need to install Node.js and npm (Node Package Manager). Node.js is a JavaScript runtime that allows us to run JavaScript code outside of a web browser, while npm is a package manager that helps us manage our project dependencies.

To install Node.js and npm, follow these steps:

  1. Visit the official Node.js website (https://nodejs.org/) and download the latest LTS (Long Term Support) version of Node.js for your operating system.
  2. Run the installer and follow the instructions to install Node.js.
  3. After the installation is complete, open a terminal or command prompt and run the following command to verify that Node.js and npm are installed correctly:
node -v
npm -v

If the above commands display the version numbers of Node.js and npm, respectively, then the installation was successful.

Creating a new Angular project

Now that we have Node.js and npm installed, we can create a new Angular project. Angular provides a command-line interface (CLI) that makes it easy to scaffold and manage Angular projects.

To create a new Angular project, follow these steps:

  1. Open a terminal or command prompt and navigate to the directory where you want to create your project.
  2. Run the following command to install the Angular CLI globally:
npm install -g @angular/cli
  1. After the installation is complete, run the following command to create a new Angular project:
ng new my-app

Replace "my-app" with the name of your project.

  1. Navigate to the project directory:
cd my-app

Adding Electron to the project

With our Angular project set up, we can now add Electron to the project. Electron provides a command-line tool called electron-forge that helps us initialize and manage Electron applications.

To add Electron to the project, follow these steps:

  1. In the project directory, run the following command to install electron-forge:
npm install --save-dev electron-forge
  1. After the installation is complete, run the following command to initialize the Electron application:
npx electron-forge init

This command will create a new directory called src-electron that contains the Electron-specific files and configuration for our application.

  1. To start the Electron application, run the following command:
npm run electron

This will launch the Electron application window and display the default Electron application.

Building the User Interface

Now that we have our development environment set up, we can start building the user interface of our desktop application. In this section, we will cover topics such as creating components, styling with Angular Material, and handling user interactions.

Creating components

Components are the building blocks of an Angular application. They encapsulate the HTML, CSS, and JavaScript/TypeScript code that defines a specific part of the user interface. In our case, we will create components to represent different parts of our desktop application, such as the main window, navigation bar, and content area.

To create a component, follow these steps:

  1. In the project directory, run the following command to generate a new component:
ng generate component main-window

Replace "main-window" with the name of your component.

  1. This command will generate the necessary files and code for the component in the src/app directory. Open the generated component files in your code editor and customize them according to your needs.

  2. Repeat the above steps to create other components for different parts of your application.

Using Angular Material for styling

Angular Material is a UI component library for Angular that provides pre-built UI components and styles that follow the Material Design guidelines. It makes it easy to create a visually appealing and consistent user interface for our desktop application.

To use Angular Material in our project, follow these steps:

  1. In the project directory, run the following command to install Angular Material and its dependencies:
ng add @angular/material
  1. After the installation is complete, import the necessary Angular Material modules in your component files. For example, to use a button component, import the MatButtonModule module in your component's module file:
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [MainWindowComponent],
  imports: [MatButtonModule],
})
export class MainWindowModule {}
  1. Use the Angular Material components in your component's HTML template. For example, to use a button component, add the following code to your component's HTML template:
<button mat-button>Click me</button>

Handling user interactions

In addition to styling the user interface, we also need to handle user interactions in our desktop application. This includes responding to button clicks, form submissions, and other user actions.

To handle user interactions in our Angular application, we can use event binding and template expressions. Event binding allows us to listen for specific events, such as button clicks, and execute a function when the event occurs. Template expressions allow us to evaluate and display dynamic values in our HTML templates.

To handle a button click event, follow these steps:

  1. In your component's HTML template, add the following code to bind the button's click event to a function in your component's TypeScript code:
<button mat-button (click)="handleButtonClick()">Click me</button>
  1. In your component's TypeScript code, define the handleButtonClick() function and implement the desired logic:
handleButtonClick() {
  // Handle the button click event
  console.log('Button clicked');
}

When the button is clicked, the handleButtonClick() function will be executed, and the message "Button clicked" will be logged to the console.

Integrating with Electron

Now that we have our user interface built using Angular, we can integrate it with Electron to create a desktop application. Electron provides two main processes: the main process and the renderer process. The main process represents the main entry point of the application and manages the lifecycle of the application window, while the renderer process is responsible for rendering the user interface and handling user interactions.

Electron main process

The Electron main process is responsible for creating and managing the application window, handling system events, and controlling the lifecycle of the application. It runs in a separate Node.js process and communicates with the renderer process using interprocess communication (IPC).

To interact with the Electron main process in our Angular application, we can use the electron and electron-store packages. The electron package provides access to Electron APIs, while the electron-store package provides a simple way to persist and retrieve application settings.

To use the Electron main process in our application, follow these steps:

  1. In the src-electron/main.ts file, add the following code to initialize the Electron main process:
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
import * as url from 'url';
import { Store } from 'electron-store';

let mainWindow: BrowserWindow | null;
const store = new Store();

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

  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true,
    })
  );

  // Handle window closed event
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

// Handle app ready event
app.on('ready', () => {
  createWindow();
});

// Handle app window all closed event
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Handle app activate event
app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

// Handle IPC messages from the renderer process
ipcMain.on('get-settings', (event) => {
  event.returnValue = store.get('settings');
});

This code sets up the Electron main process, creates a new application window, loads the Angular application, and handles various events such as window closed, app ready, window all closed, and activate. It also handles IPC messages from the renderer process to retrieve application settings.

  1. In your Angular component's TypeScript code, add the following code to send an IPC message to the main process and retrieve the application settings:
import { ipcRenderer } from 'electron';

function getSettings() {
  const settings = ipcRenderer.sendSync('get-settings');
  console.log(settings);
}

This code uses the ipcRenderer object from the electron package to send a synchronous IPC message with the name "get-settings" to the main process and retrieve the application settings.

Electron renderer process

The Electron renderer process is responsible for rendering the user interface of the application and handling user interactions. It runs in a separate Chromium process and communicates with the main process using IPC.

To interact with the Electron renderer process in our Angular application, we can use the electron package. The electron package provides access to Electron APIs, such as the ipcRenderer object for sending and receiving IPC messages.

To use the Electron renderer process in our application, follow these steps:

  1. In your Angular component's TypeScript code, import the ipcRenderer object from the electron package:
import { ipcRenderer } from 'electron';
  1. Use the ipcRenderer object to send and receive IPC messages. For example, to send an IPC message to the main process, use the send() method:
ipcRenderer.send('message', 'Hello from renderer process');
  1. To receive an IPC message from the main process, use the on() method:
ipcRenderer.on('reply', (event, message) => {
  console.log(message);
});

This code sends an IPC message with the name "message" and the payload "Hello from renderer process" to the main process. It also listens for an IPC message with the name "reply" and logs the message to the console when it is received.

Interprocess communication

Interprocess communication (IPC) is a mechanism that allows different processes to communicate with each other. In the context of Electron, IPC is used to enable communication between the main process and the renderer process.

Electron provides two types of IPC: synchronous and asynchronous. Synchronous IPC blocks the sender until a reply is received, while asynchronous IPC does not block the sender and allows multiple messages to be sent and received concurrently.

To send and receive IPC messages in our Angular application, we can use the ipcRenderer object in the renderer process and the ipcMain object in the main process.

To send an IPC message from the renderer process to the main process, follow these steps:

  1. In your Angular component's TypeScript code, import the ipcRenderer object from the electron package:
import { ipcRenderer } from 'electron';
  1. Use the ipcRenderer object to send an IPC message. For example, to send a message with the name "message" and the payload "Hello from renderer process", use the send() method:
ipcRenderer.send('message', 'Hello from renderer process');

To receive an IPC message in the main process, follow these steps:

  1. In the src-electron/main.ts file, add the following code to handle the IPC message:
ipcMain.on('message', (event, message) => {
  console.log(message);
});

This code listens for an IPC message with the name "message" and logs the message to the console when it is received.

Packaging and Distributing the App

Once we have finished building our desktop application, we need to package and distribute it to end users. Electron provides tools and utilities that make it easy to package our application for different platforms, create installers, and publish the app to distribution channels.

Building for different platforms

To package our Electron application for different platforms, we can use the electron-packager package. This package allows us to specify the target platform, architecture, and other options, and creates a distributable package for the specified platform.

To package our Electron application for Windows, follow these steps:

  1. In the project directory, run the following command to install electron-packager:
npm install --save-dev electron-packager
  1. After the installation is complete, add the following script to the scripts section of the package.json file:
"scripts": {
  "package-win": "electron-packager . my-app --platform=win32 --arch=x64 --out=dist/ --icon=src-electron/icon.ico"
}

This script uses electron-packager to package the Electron application for Windows 64-bit. It specifies the output directory as dist/ and the icon file as src-electron/icon.ico.

  1. Run the following command to package the Electron application for Windows:
npm run package-win

This command will create a distributable package for Windows in the dist/ directory.

Creating installers

To create installers for our packaged Electron application, we can use the electron-builder package. This package allows us to create installers for different platforms, such as Windows, macOS, and Linux, with customizable options and settings.

To create an installer for our packaged Electron application, follow these steps:

  1. In the project directory, run the following command to install electron-builder:
npm install --save-dev electron-builder
  1. After the installation is complete, add the following script to the scripts section of the package.json file:
"scripts": {
  "build-installer": "electron-builder"
}

This script uses electron-builder to create an installer for the packaged Electron application. It uses the default configuration specified in the electron-builder.json file.

  1. Run the following command to create the installer:
npm run build-installer

This command will create an installer for the packaged Electron application in the dist/ directory, according to the specified configuration.

Publishing the app

Once we have created the installer for our Electron application, we can publish it to distribution channels, such as an app store or a website, to make it available to end users.

To publish our Electron application, follow these steps:

  1. Sign up for a developer account on the desired distribution channel, if necessary.

  2. Prepare the necessary files and information for publishing, such as the installer file, screenshots, description, and release notes.

  3. Follow the instructions provided by the distribution channel to publish the app. This may involve creating a new app listing, filling out the required information, and uploading the necessary files.

  4. Once the app is published, it will be available for download and installation by end users through the distribution channel.

Testing and Debugging

Testing and debugging are important steps in the software development process. In this section, we will cover topics such as unit testing Angular components, debugging Electron processes, and end-to-end testing.

Unit testing Angular components

Unit testing is the process of testing individual units of code, such as functions or components, to ensure that they behave as expected. Angular provides a built-in testing framework called Jasmine that makes it easy to write and run unit tests for Angular components.

To write a unit test for an Angular component, follow these steps:

  1. In the project directory, run the following command to generate a new unit test file for the component:
ng generate component my-component --spec

Replace "my-component" with the name of your component.

  1. This command will generate the necessary test file in the src/app directory. Open the generated test file in your code editor and customize it according to your needs.

  2. Write test cases using the describe() and it() functions provided by Jasmine. For example, to test a component method that adds two numbers, you can write the following test case:

describe('MyComponent', () => {
  it('should add two numbers', () => {
    const component = new MyComponent();
    const result = component.addNumbers(2, 3);
    expect(result).toBe(5);
  });
});

This code creates a new instance of the component, calls the addNumbers() method with two numbers, and expects the result to be 5.

  1. Run the following command to execute the unit tests:
ng test

This command will run all the unit tests in the project and display the test results in the terminal.

Debugging Electron processes

When developing an Electron application, we may encounter bugs or issues that require debugging. Electron provides a built-in debugging tool called DevTools that allows us to inspect and debug the renderer process and the main process.

To debug the renderer process, follow these steps:

  1. Launch the Electron application with the --inspect flag:
npm run electron -- --inspect
  1. Open Google Chrome and navigate to chrome://inspect.

  2. Under the "Remote Target" section, you should see your Electron application listed. Click the "Inspect" button to open DevTools for the renderer process.

  3. Use the DevTools console, sources, and other debugging features to inspect and debug the renderer process.

To debug the main process, follow these steps:

  1. In the src-electron/main.ts file, add the following code to enable debugging for the main process:
import { app } from 'electron';

app.commandLine.appendSwitch('remote-debugging-port', '9222');
  1. Launch the Electron application with the --remote-debugging-port flag:
npm run electron -- --remote-debugging-port=9222
  1. Open Google Chrome and navigate to chrome://inspect.

  2. Under the "Remote Target" section, you should see your Electron application listed. Click the "Inspect" button to open DevTools for the main process.

  3. Use the DevTools console, sources, and other debugging features to inspect and debug the main process.

End-to-end testing

End-to-end (E2E) testing is the process of testing the entire application from start to finish to ensure that it behaves as expected. Angular provides a built-in testing framework called Protractor that makes it easy to write and run E2E tests for Angular applications.

To write an E2E test for an Angular application, follow these steps:

  1. In the project directory, run the following command to generate a new E2E test file:
ng generate e2e my-app-e2e

Replace "my-app" with the name of your Angular project.

  1. This command will generate the necessary test file in the e2e directory. Open the generated test file in your code editor and customize it according to your needs.

  2. Write test cases using the describe() and it() functions provided by Protractor. For example, to test the navigation to a specific page, you can write the following test case:

describe('App', () => {
  it('should navigate to the about page', () => {
    browser.get('/');
    element(by.linkText('About')).click();
    expect(browser.getCurrentUrl()).toContain('/about');
  });
});

This code navigates to the home page, clicks on a link with the text "About", and expects the current URL to contain "/about".

  1. Run the following command to execute the E2E tests:
ng e2e

This command will launch the application in a headless browser and run all the E2E tests in the project. The test results will be displayed in the terminal.

Conclusion

In this tutorial, we have learned how to build desktop applications using Angular and Electron. We started by setting up the development environment, including installing Node.js and npm, creating a new Angular project, and adding Electron to the project. We then covered topics such as building the user interface with components and Angular Material, integrating with Electron using the main process and renderer process, and packaging and distributing the app for different platforms. Finally, we discussed testing and debugging techniques, including unit testing Angular components, debugging Electron processes, and end-to-end testing. With this knowledge, you are now ready to start building your own desktop applications using Angular and Electron. Happy coding!