Integrating an Angular-CLI application with Electron - The IPC
Tags: angular, electron, node
Published: 2018-01-29 20:36:49

---
title: Integrating an Angular-CLI application with Electron - The IPC
published: true
description: A story about how to integrate an angular-cli application with Electron using IPC
tags: angular, electron, node
---

# Previously on...

In the previous sections and stories, I explained how to integrate an Angular-CLI generated application with Electron, and also how to write the same Electron application in Typescript. So far, this would allow a simple Angular application being packed as a Electron application, but there is no way for us to interact with Electron's main thread.

- [The seed](https://dev.to/michaeljota/integrating-an-angular-cli-application-with-electron-34mi)
- [Electron with Typescript](https://dev.to/michaeljota/electron-with-typescript-using-ts-node-8oi)

# Introduction

In this post I'll try to explain how to get a real integrating between Angular and Electron, and be able to communicate using Electron's IPC.

## What is IPC?

IPC is the inter-process communication module of Electron. With that you can send messages between your web application and the main thread application. To send and receive messages in the main thread you would have to use the [ipcMain](https://electronjs.org/docs/api/ipc-main) function property. Likewise, for the renderer process you would like to use the [ipcRenderer](https://electronjs.org/docs/api/ipc-renderer).

## How to use ipcMain?

Use ipcMain is a simple as require it, and use one of the functions that are able for us. 
```typescript
import { ipcMain } from 'electron';

ipcMain.on('ping', (event) => {
    logger('ping'); // Assume there is a logger function that would display 'ping' in console. console object does not work as-is, and it's not so easy to configure it.
}
```
With that, the application will be listening a `'ping'` event, and will print `ping` in the console. Easy peasy.

## How to (normally) use ipcRenderer?

In a normal scenario, a simple `require('electron').ipcRenderer` would give us access to the IPC in the renderer thread. So, following the previous example, we could do something like:

```typescript
const { ipcRenderer } = require('electron');

ipcRenderer.send('ping');
```

This will invoke the callback function in the main thread. 

But, this won't work in the Angular-CLI application. Angular-CLI underneath uses Webpack, and thus the latter will find the `require` _key_ word, interpret as a call for the Node.js' `require` function, and will try to resolve the 'electron' module. 

## A Story about two `require`s

`require` as been with us for a while, since the first version of Node back in 2008, almost 10 years. But still is one of the most misunderstand functions in modern web development. With the integration of `import` and `export` keywords in Node, several articles was written to explain how the `require` function currently works. TL;DR: A `require` function is injected for every file, allowing Node to resolve the dependencies. Later, module builders will look for `import` and `require` and will try to resolve modules, assuming that's what you want. 

So, now that we know that `require` is actually a function injected by Node, then how is `require` able to work in Electron renderer process. You may guessed it, Electron injects its own version of `require` function in the global scope of the renderer process when it loads the page. So, although it may seems like the same function, it is not. 

## How to use ipcRenderer in a Angular-CLI application? (Or any application bundle with Webpack)

To use `ipcRenderer` in our Angular-CLI app, we will leverage on the global scope. No, we won't call ipcRenderer in the global context, although we could make this works, it's not ideal. But I just told that `require` is a function that Electron injects in the global context. So, can we just use `require`? No. That's because, as I also told, Webpack will try to resolve the module requirement. There is actually another way to access a global variable, that's with the `window` object. The `window` object by default will have all the global variables, including `require`.

So we can just use `window.require` in any part of our application and it would work as expected. In this context, you cannot use `window.require` to require any module in your Angular-CLI application, but you can load any module that you had set in your Electron application.

## Writing the service

For this example we will expose the `ipc` as an Angular service, and will create it using angular-cli. We follow the guide about [services](https://github.com/angular/angular-cli/wiki/generate-service)

```shell
ng generate service ipc -m app
```

This will create our service, and update our `app.module` to include it in the Angular application.

Then, we write the code. We start by importing the IpcRenderer interface from electron module

```typescript
import { IpcRenderer } from 'electron';

```

But, we don't have any Electron module in our Angular project, how will it be resolved? We'll, actually we don't need to have the Electron module in our Angular project, because as Typescript resolver work, it will look in `node_modules` in folders that are children from ours project. If you want to be extra safe, or if for any reason this is not a desire behavior, you could install the `@types` of electron, and it won't load the hole package.

```shell
npm install @types/electron
```

Next, we add a reference property inside the class to save the ipcRenderer function when we load it.

```typescript
  private _ipc: IpcRenderer | undefined;
```

It's important to typed it as `IpcRenderer | undefined` for compile the code in strict mode, as we may or may not be able to load the ipcRenderer. We now write the constructor, to assign the `_ipc` in load time. 

```typescript
  constructor() {
    if (window.require) {
      try {
        this._ipc = window.require('electron').ipcRenderer;
      } catch (e) {
        throw e;
      }
    } else {
      console.warn('Electron\'s IPC was not loaded');
    }
  }
```

As you can see, we will first check if `window` object has a `require` property. With this we will assume we are inside Electron, then we will `try` to `require('electron')`, if for any reason it doesn't work it just throw an error, and the property `_ipc` will be undefined. Checking require in the `window` object will allow us to run the service in a regular browser context, in that case the `_ipc` won't have a assignment value and will be `undefined`.

You should have Typescript complaining about `window` not having a `require` property, so we need to update the project's typings file. Open `/src/typings.d.ts` and update with the following lines:

```typescript
interface Window {
  require: NodeRequire;
}
```
Now, Typescript shouldn't be annoying us. 

I'll add a couple of functions to the service, just to test that it actually works as expected.

```typescript
  public on(channel: string, listener: Function): void {
    if (!this._ipc) {
      return;
    }
    this._ipc.on(channel, listener);
  }

  public send(channel: string, ...args): void {
    if (!this._ipc) {
      return;
    }
    this._ipc.send(channel, ...args);
  }
```

As you can see, in both we check for the `_ipc` property to be assigned, and then we call the functions that we want to call. We expose the same function interface of the functions we want to call, so it will be very intuitive to call them from our application. 

The final service should look like:

```typescript
import { Injectable } from '@angular/core';
import { IpcRenderer } from 'electron';

@Injectable()
export class IpcService {
  private _ipc: IpcRenderer | undefined = void 0;

  constructor() {
    if (window.require) {
      try {
        this._ipc = window.require('electron').ipcRenderer;
      } catch (e) {
        throw e;
      }
    } else {
      console.warn('Electron\'s IPC was not loaded');
    }
  }

  public on(channel: string, listener: IpcCallback): void {
    if (!this._ipc) {
      return;
    }
    this._ipc.on(channel, listener);
  }

  public send(channel: string, ...args): void {
    if (!this._ipc) {
      return;
    }
    this._ipc.send(channel, ...args);
  }

}
```

## Testing it

For testing we will call an ipc channel, and make Electron to response us back, and listen that response. 

First, we will update our `app.component` with the following constructor function:
```typescript
  constructor(private readonly _ipc: IpcService) {
    this._ipc.on('pong', (event: Electron.IpcMessageEvent) => {
      console.log('pong');
    });

    this._ipc.send('ping');
  }
```

And then we will update Electron's `index.ts` file, importing the `ipcMain` module and setting a listener for the `ping` event, that response `pong`.

```typescript
// First we update the import line
import { app, BrowserWindow, ipcMain, IpcMessageEvent } from 'electron';
...
ipcMain.on('ping', (event: IpcMessageEvent) => {
  event.sender.send('pong');
});
```

Run the angular app with `npm run electron:start` and in the electron application run `npm start`. You should see a pong getting logged.

![pong](https://thepracticaldev.s3.amazonaws.com/i/xfyeh5gf2dvz66o54qdk.png)

## Moving forward

There are still somethings that can be improve in the current workflow, and some of you are having troubles with native modules. But, so far we have pass for a simple Angular app, to a complete Angular/Electron integration. I'll soon uploading all this to Github, to stay tune there as well. 

## That's all folks

As usual, thanks you for reading this, check out my others posts. Give the post love, and share it with your friends. See you next time.