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.
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:
- 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.
- Run the installer and follow the instructions to install Node.js.
- 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:
- Open a terminal or command prompt and navigate to the directory where you want to create your project.
- Run the following command to install the Angular CLI globally:
npm install -g @angular/cli
- 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.
- 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:
- In the project directory, run the following command to install
electron-forge
:
npm install --save-dev electron-forge
- 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.
- 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:
- 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.
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.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:
- In the project directory, run the following command to install Angular Material and its dependencies:
ng add @angular/material
- 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 {}
- 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:
- 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>
- 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:
- 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.
- 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:
- In your Angular component's TypeScript code, import the
ipcRenderer
object from theelectron
package:
import { ipcRenderer } from 'electron';
- Use the
ipcRenderer
object to send and receive IPC messages. For example, to send an IPC message to the main process, use thesend()
method:
ipcRenderer.send('message', 'Hello from renderer process');
- 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:
- In your Angular component's TypeScript code, import the
ipcRenderer
object from theelectron
package:
import { ipcRenderer } from 'electron';
- 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 thesend()
method:
ipcRenderer.send('message', 'Hello from renderer process');
To receive an IPC message in the main process, follow these steps:
- 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:
- In the project directory, run the following command to install
electron-packager
:
npm install --save-dev electron-packager
- After the installation is complete, add the following script to the
scripts
section of thepackage.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
.
- 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:
- In the project directory, run the following command to install
electron-builder
:
npm install --save-dev electron-builder
- After the installation is complete, add the following script to the
scripts
section of thepackage.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.
- 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:
Sign up for a developer account on the desired distribution channel, if necessary.
Prepare the necessary files and information for publishing, such as the installer file, screenshots, description, and release notes.
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.
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:
- 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.
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.Write test cases using the
describe()
andit()
functions provided byJasmine
. 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.
- 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:
- Launch the Electron application with the
--inspect
flag:
npm run electron -- --inspect
Open Google Chrome and navigate to
chrome://inspect
.Under the "Remote Target" section, you should see your Electron application listed. Click the "Inspect" button to open DevTools for the renderer process.
Use the DevTools console, sources, and other debugging features to inspect and debug the renderer process.
To debug the main process, follow these steps:
- 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');
- Launch the Electron application with the
--remote-debugging-port
flag:
npm run electron -- --remote-debugging-port=9222
Open Google Chrome and navigate to
chrome://inspect
.Under the "Remote Target" section, you should see your Electron application listed. Click the "Inspect" button to open DevTools for the main process.
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:
- 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.
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.Write test cases using the
describe()
andit()
functions provided byProtractor
. 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".
- 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!