Chapter 5: Building a WYSIWYG Editor for the Desktop using Electron

Web applications are traditionally built with HTML, CSS, and JavaScript. Their use has also been widely spread to server development using Node.js. Various tools and frameworks have emerged in recent years that use HTML, CSS, and JavaScript to create applications for desktop and mobile. In this chapter, we are going to investigate how to create desktop applications using Angular and Electron.

Electron is a JavaScript framework that is used to build native desktop applications with web technologies. If we combine it with the Angular framework, we can create fast and highly performant web applications. In this chapter, we will build a desktop WYSIWYG editor and cover the following topics:

  • Adding a WYSIWYG editor library for Angular
  • Integrating Electron in the workspace
  • Communicating between Angular and Electron
  • Packaging a desktop application

Essential background theory and context

Electron is a cross-platform framework that is used to build desktop applications for Windows, Linux, and Mac. Many popular applications are built with Electron, such as Visual Studio Code, Skype, and Slack. The Electron framework is built on top of Node.js and Chromium. Web developers can leverage their existing HTML, CSS, and JavaScript skills to create desktop applications without learning a new language such as C++ or C#.

Tip

Electron applications have many similarities with PWA applications. Consider building an Electron application for scenarios such as advanced filesystem manipulation or when you need a more native look and feel for your application. Another use case is when you are building a complementary tool for your primary desktop product and you want to ship them together.

An Electron application consists of two processes:

  • Main: This interacts with the native local resources using the Node.js API.
  • Renderer: This is responsible for managing the user interface of the application.

An Electron application can have only one main process that can communicate with one or more renderer processes. Each renderer process operates in complete isolation from the others.

The Electron framework provides the ipcMain and ipcRenderer interfaces, which we can use to interact with these processes. The interaction is accomplished using Inter-Process Communication (IPC), a mechanism that exchanges messages securely and asynchronously over a common channel via a Promise-based API.

Project overview

In this project, we will build a desktop WYSIWYG editor that keeps its content local to the filesystem. Initially, we will build it as an Angular application using ngx-wig, a popular WYSIWYG Angular library. We will then convert it to a desktop application using Electron and learn how to sync content between Angular and Electron. We will also see how to persist the content of the editor into the filesystem. Finally, we will package our application as a single executable file that can be run in a desktop environment.

Build time: 1 hour.

Getting started

The following software tools are required to complete this project:

Adding a WYSIWYG editor library for Angular

We will kick off our project by creating a WYSIWYG editor as a standalone Angular application first. Use the Angular CLI to create a new Angular application from scratch:

ng new my-editor --defaults

We pass the following options to the ng new command:

  • my-editor: Defines the name of the application
  • --defaults: Defines CSS as the preferred stylesheet format of the application and disables routing because our application will consist of a single component that will host the editor

A WYSIWYG editor is a rich text editor, such as Microsoft Word. We could create one from scratch using the Angular framework, but it would be a time-consuming process, and we would only re-invent the wheel. The Angular ecosystem contains a wide variety of libraries to use for this purpose. One of them is the ngx-wig library, which has no external dependencies, just Angular! Let's add the library to our application and learn how to use it:

  1. Use the npm client to install ngx-wig from the npm package registry:

    npm install ngx-wig

  2. Open the app.module.ts file and add NgxWigModule into the imports array of the @NgModule decorator:

    import { NgModule } from '@angular/core';

    import { BrowserModule } from '@angular/platform-browser';

    import { NgxWigModule } from 'ngx-wig';

    import { AppComponent } from './app.component';

    @NgModule({

      declarations: [

        AppComponent

      ],

      imports: [

        BrowserModule,

        NgxWigModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

    NgxWigModule is the main module of the ngx-wig library.

  3. Create a new Angular component that will host our WYSIWYG editor:

    ng generate component editor

  4. Open the template file of the newly generated component, editor.component.html, and replace its content with the following HTML snippet:

    <ngx-wig placeholder="Enter your content"></ngx-wig>

    NgxWigModule exposes a set of Angular services and components that we can use in our application. The main component of the module is the ngx-wig component, which displays the actual WYSIWYG editor. It exposes a collection of input properties that we can set, such as the placeholder of the editor. You can find more options at https://github.com/stevermeister/ngx-wig.

  5. Open the app.component.html file and replace its content with the app-editor selector:

    <app-editor></app-editor>

  6. Open the styles.css file, which contains global styles for the Angular application, and add the following styles to make the editor dockable and take up the full page:

    html, body {

      margin: 0;

      width: 100%;

      height: 100%;

    }

    .ng-wig, .nw-editor-container, .nw-editor  {

      display: flex !important;

      flex-direction: column;

      height: 100% !important;

      overflow: hidden;

    }

  7. Open the main HTML file of the Angular application, index.html, and remove the base tag from the head element. The base tag is used from the browser to reference scripts and CSS files with a relative URL. Leaving the base tag will make our desktop application fail because it will load all necessary assets directly from the local filesystem. We will learn more in the Integrating Angular with Electron section.

Let's see what we have achieved so far. Run ng serve and navigate to http://localhost:4200 to preview the application:

Figure 5.1 – Application output

Figure 5.1 – Application output

Our application consists of the following:

  • A toolbar with buttons that allows us to apply different styles to the content of the editor
  • A text area that is used as the main container for adding content to the editor

We now have created a web application using Angular that features a fully operational WYSIWYG editor. In the following section, we will learn how to convert it into a desktop one using Electron.

Integrating Electron in the workspace

The Electron framework is an npm package that we can install using the following command:

npm install -D electron

The previous command will install the latest version of the electron npm package into the Angular CLI workspace. It will also add a respective entry into the devDependencies section of the package.json file of our project.

Important Note

Electron is added to the devDependencies section of the package.json file because it is a development dependency of our application. It is used only to prepare and build our application as a desktop one and not during runtime.

Electron applications run on the Node.js runtime and use the Chromium browser for rendering purposes. A Node.js application has at least a JavaScript file, usually called index.js or main.js, which is the main entry point of the application. Since we are using Angular and TypeScript as our development stack, we will start by creating a respective TypeScript file that will be finally compiled to JavaScript:

  1. Create a folder named electron inside the src folder of the Angular CLI workspace. The electron folder will contain any source code that is related to Electron.

    Tip

    We can think of our application as two different platforms. The web platform is the Angular application, which resides in the srcapp folder. The desktop platform is the Electron application, which resides in the srcelectron folder. This approach has many benefits, including that it enforces the separation of concerns in our application and allows each one to develop independently from the other. From now on, we will refer to them as the Angular application and the Electron application.

  2. Create a main.ts file inside the electron folder with the following content:

    import { app, BrowserWindow } from 'electron';

    function createWindow () {

      const mainWindow = new BrowserWindow({

        width: 800,

        height: 600

      });

      mainWindow.loadFile('index.html');

    }

    app.whenReady().then(() => {

      createWindow();

    });

    We first import the BrowserWindow and app artifacts from the electron npm package. The BrowserWindow class is used to create a desktop window for our application. We define the window dimensions, passing an options object in its constructor that sets the width and height values of the window. We then call the loadFile method, passing as a parameter the HTML file that we want to load inside the window.

    Important Note

    The index.html file that we pass in the loadFile method is the main HTML file of the Angular application. It will be loaded using the file:// protocol, which is why we removed the base tag in the Adding a WYSIWYG Angular library section.

    The app object is the global object of our desktop application, just like the window object on a web page. It exposes a whenReady promise that, when resolved, means we can run any initialization logic for our application, including the creation of the window.

  3. Create a tsconfig.json file inside the electron folder and add the following contents:

    {

      "extends": "../../tsconfig.json",

      "compilerOptions": {

        "importHelpers": false

      },

      "include": [

        "**/*.ts"

      ]

    }

    The main.ts file needs to be compiled to JavaScript because browsers currently do not understand TypeScript. The compilation process is called transpilation and requires a TypeScript configuration file. The configuration file contains options that drive the TypeScript transpiler, which is responsible for the transpilation process.

    The preceding TypeScript configuration file defines the path of the Electron source code files using the include property and sets the importHelpers property to false.

    Important Note

    If we enable the importHelpers flag, it will include helpers from the tslib library into our application, resulting in a larger bundle size.

  4. Run the following command to install the webpack CLI:

    npm install -D webpack-cli

    The webpack CLI is used to invoke webpack, a popular module bundler, from the command line. We will use webpack to build and bundle our Electron application.

  5. Install the ts-loader npm package using the following npm command:

    npm install -D ts-loader

    The ts-loader library is a webpack plugin that can load TypeScript files.

We have now created all the individual pieces needed to convert our Angular application into a desktop one using Electron. We only need to put them together so that we can build and run our desktop application. The main piece that orchestrates the Electron application is the webpack configuration file that we need to create in the root folder of our Angular CLI workspace:

webpack.config.js

const path = require('path');

const src = path.join(process.cwd(), 'src', 'electron');

module.exports = {

  mode: 'development',

  devtool: 'source-map',

  entry: path.join(src, 'main.ts'),

  output: {

    path: path.join(process.cwd(), 'dist', 'my-editor'),

    filename: 'shell.js'

  },

  module: {

    rules: [

      {

        test: /.ts$/,

        loader: 'ts-loader',

        options: {

          configFile: path.join(src, 'tsconfig.json')

        }

      }

    ]

  },

  target: 'electron-main'

};

The preceding file configures webpack in our application using the following options:

  • mode: Indicates that we are currently running in a development environment.
  • devtool: Enables source map file generation for debugging purposes.
  • entry: Indicates the main entry point of the Electron application, which is the main.ts file.
  • output: Defines the path and the filename of the Electron bundle that will be generated from webpack. The path property points to the same folder that is used from the Angular CLI to generate the bundle of the Angular application. The filename property is set to shell.js because the default one generated from webpack is main.js, and it will cause a conflict with the main.js file generated from the Angular application.
  • module: Instructs webpack to load the ts-loader plugin for handling TypeScript files.
  • target: Indicates that we are currently running in the main process of Electron.

webpack now contains all information needed to build and bundle the Electron application. On the other hand, the Angular CLI takes care of building the Angular application. Let's see how we can combine them and run our desktop application:

  1. Run the following npm command to install the concurrently npm package:

    npm install -D concurrently

    The concurrently library enables us to execute multiple processes concurrently. In our case, it will enable us to run the Angular and Electron applications in parallel.

  2. Open the package.json file and add a new entry in the scripts property:

    "scripts": {

      "ng": "ng",

      "start": "ng serve",

      "build": "ng build",

      "watch": "ng build --watch --configuration

         development",

      "test": "ng test",

      "start:desktop": "concurrently "ng build --delete-

         output-path=false --watch" "webpack --watch""

    }

    The start:desktop script builds the Angular application using the ng build command of the Angular CLI and the Electron application using the webpack command. Both applications run in watch mode using the --watch option, so that every time we make a change in the code, the application will rebuild to reflect the change. Whenever we modify the Angular application, the Angular CLI will delete the dist folder by default. We can prevent this behavior using the --delete-output-path=false option because the Electron application is also built in the same folder.

    Important Note

    We did not pass the webpack configuration file to the webpack command because it assumes the webpack.config.js filename by default.

  3. Click on the Run menu that exists in the sidebar of Visual Studio Code:
    Figure 5.2 – Run menu

    Figure 5.2 – Run menu

  4. In the RUN pane that appears, click on the create a launch.json file link:
    Figure 5.3 – RUN pane

    Figure 5.3 – RUN pane

  5. Visual Studio Code will open a drop-down menu that allows us to select the environment to run our application. An Electron application uses Node.js, so select any of the available Node.js options.
  6. Visual Studio Code will create a .vscode folder in our Angular CLI workspace with a launch.json file inside it. In the launch.json file that has been opened, set the value of the program property to ${workspaceRoot}/dist/my-editor/shell.js. The program property indicates the absolute path of the Electron bundle file.
  7. Add the following entry below the program property in the launch.json file:

    "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron"

    The runtimeExecutable property defines the absolute path of the Electron executable.

We are now ready to run our desktop application and preview it. Run the following npm command to build the application:

npm run start:desktop

The previous command will build first the Electron application and then the Angular one. Wait for the Angular build to finish and then press F5 to preview the application:

Figure 5.4 – Application window

Figure 5.4 – Application window

In the preceding screenshot, we can see that our Angular application with the WYSIWYG editor is hosted inside a native desktop window. It contains the following characteristics that we usually find in desktop applications:

  • The header with an icon
  • The main menu
  • Minimize, maximize, and close buttons

The Angular application is rendered inside the Chromium browser. To verify that, click on the View menu item and select the Toggle Developer Tools option.

Well done! You have successfully managed to create your own desktop WYSIWYG editor. In the following section, we will learn how to interact between Angular and Electron.

Communicating between Angular and Electron

According to the specifications of the project, the content of the WYSIWYG editor needs to be persisted in the local filesystem. Additionally, the content will be loaded from the filesystem upon application startup.

The Angular application handles any interaction between the WYSIWYG editor and its data using the renderer process, whereas the Electron application manages the filesystem with the main process. Thus, we need to establish an IPC mechanism to communicate between the two Electron processes as follows:

  • Configuring the Angular CLI workspace
  • Interacting with the editor
  • Interacting with the filesystem

Let's start with the first one, to set up the Angular CLI project for supporting the desired communication mechanism.

Configuring the Angular CLI workspace

We need to modify several files to configure the workspace of our application:

  1. Open the main.ts file that exists in the srcelectron folder and set the nodeIntegration property to true in the BrowserWindow constructor:

    function createWindow () {

      const mainWindow = new BrowserWindow({

        width: 800,

        height: 600,

        webPreferences: {

          nodeIntegration: true,

          contextIsolation: false

        }

      });

      mainWindow.loadFile('index.html');

    }

    The preceding flag will enable Node.js in the renderer process and expose the ipcRenderer interface, which we will need for communicating with the main process.

  2. Open the tsconfig.app.json file that exists in the root folder of the Angular CLI workspace and add the electron entry inside the types property:

    {

      "extends": "./tsconfig.json",

      "compilerOptions": {

        "outDir": "./out-tsc/app",

        "types": [

          "electron"

        ]

      },

      "files": [

        "src/main.ts",

        "src/polyfills.ts"

      ],

      "include": [

        "src/**/*.d.ts"

      ]

    }

    The Electron framework includes types that we can use in our Angular application.

  3. Create a new file named window.ts inside the srcapp folder and enter the following code:

    import { InjectionToken } from '@angular/core';

    export const WINDOW = new

      InjectionToken<Window>('Global window object', {

      factory: () => window

    });

    export interface ElectronWindow extends Window {

      require(module: string): any;

    }

    The Electron framework is a JavaScript module that can be loaded from the global window object of the browser. We use the InjectionToken interface to make the window object injectable so that we can use it in our Angular components and services. Additionally, we use a factory method to return it so that it is easy to replace it in platforms with no access to the window object, such as the server.

    Electron is loaded using the require method of the window object, which is available only in the Node.js environment. To use it in an Angular application, we create the ElectronWindow interface that extends the Window interface by defining that method.

The Angular and Electron applications are now ready to interact with each other using the IPC mechanism. Let's start implementing the necessary logic in the Angular application first.

Interacting with the editor

The Angular application is responsible for managing the WYSIWYG editor. The content of the editor is kept in sync with the filesystem using the renderer process of Electron. Let's find out how to use the renderer process:

  1. Create a new Angular service using the generate command of the Angular CLI:

    ng generate service editor

  2. Open the editor.service.ts file and inject the WINDOW token in the constructor of the EditorService class:

    import { Inject } from '@angular/core';

    import { Injectable } from '@angular/core';

    import { ElectronWindow, WINDOW } from './window';

    @Injectable({

      providedIn: 'root'

    })

    export class EditorService {

      constructor(@Inject(WINDOW) private window:

        ElectronWindow) {}

    }

  3. Create a getter property that returns the ipcRenderer object from the electron module:

    private get ipcRenderer(): Electron.IpcRenderer {

      return this.window.require('electron').ipcRenderer;

    }

    The electron module is the main module of the Electron framework that gives access to various properties, including the main and the renderer process. We also set the type of the ipcRenderer property to Electron.IpcRenderer, which is part of the built-in types of Electron.

  4. Create a method that will be called to get the content of the editor from the filesystem:

    getContent(): Promise<string> {

      return this.ipcRenderer.invoke('getContent');

    }

    We use the invoke method of the ipcRenderer property, passing the name of the communication channel as a parameter. The result of the getContent method is a Promise object of the string type since the content of the editor is raw text data. The invoke method initiates a connection with the main process through the getContent channel. In the Interacting with the filesystem section, we will see how to set up the main process for responding to the invoke method call in that channel.

  5. Create a method that will be called to save the content of the editor to the filesystem:

    setContent(content: string) {

      this.ipcRenderer.invoke('setContent', content);

    }

    The setContent method calls the invoke method of the ipcRenderer object again but with a different channel name. It also uses the second parameter of the invoke method to pass data to the main process. In this case, the content parameter will contain the content of the editor. We will see how to configure the main process for handling data in the Interacting with the filesystem section.

  6. Open the editor.component.ts file and create a myContent property to hold editor data. Also, inject EditorService in the constructor of the EditorComponent class:

    import { Component, OnInit } from '@angular/core';

    import { EditorService } from '../editor.service';

    @Component({

      selector: 'app-editor',

      templateUrl: './editor.component.html',

      styleUrls: ['./editor.component.css']

    })

    export class EditorComponent implements OnInit {

      myContent = '';

      constructor(private editorService: EditorService) {}

      ngOnInit(): void {

      }

    }

  7. Create a method that calls the getContent method of the editorService variable and execute it inside the ngOnInit method:

    ngOnInit(): void {

      this.getContent();

    }

    private async getContent() {

      this.myContent = await

        this.editorService.getContent();

    }

    We use the async/await syntax, which allows the synchronous execution of our code in promise-based method calls.

  8. Create a method that calls the setContent method of the editorService variable:

    saveContent(content: string) {

      this.editorService.setContent(content);

    }

  9. Let's wire up those methods that we have created with the template of the component. Open the editor.component.html file and add the following bindings:

    <ngx-wig placeholder="Enter your content"

    [ngModel]="myContent"

       (contentChange)="saveContent($event)"></ngx-wig>

    We use the ngModel directive to bind the model of the editor to the myContent component property, which will be used to display the content initially. We also use the contentChange event binding to save the content of the editor whenever it changes, that is, while the user types.

  10. The ngModel directive is part of the @angular/forms npm package. Import FormsModule in the app.module.ts file to use it:

    import { NgModule } from '@angular/core';

    import { BrowserModule } from '@angular/platform-browser';

    import { NgxWigModule } from 'ngx-wig';

    import { AppComponent } from './app.component';

    import { EditorComponent } from './editor/editor.component';

    import { FormsModule } from '@angular/forms';

    @NgModule({

      declarations: [

        AppComponent,

        EditorComponent

      ],

      imports: [

        BrowserModule,

        FormsModule,

        NgxWigModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

We have now implemented all the logic for our Angular application to communicate with the main process. It is now time to implement the other end of the communication mechanism, the Electron application, and its main process.

Interacting with the filesystem

The main process interacts with the filesystem using the fs Node.js library, which is built into the Electron framework. Let's see how we can use it:

  1. Open the main.ts file that exists in the srcelectron folder and import the following artifacts:

    import { app, BrowserWindow, ipcMain } from 'electron';

    import * as fs from 'fs';

    import * as path from 'path';

    The fs library is responsible for interacting with the filesystem. The path library provides utilities for working with file and folder paths. The ipcMain object allows us to work with the main process of Electron.

  2. Create a variable that holds the path of the file containing the content of the editor:

    const contentFile = path.join(app.getPath('userData'), 'content.html');

    The file that keeps the content of the editor is the content.html file that exists inside the reserved userData folder. The userData folder is an alias for a special purpose system folder, different for each OS, and it is used to store application-specific files such as configuration. You can find more details about the userData folder as well as other system folders at https://www.electronjs.org/docs/api/app#appgetpathname.

    Important Note

    The getPath method of the app object works cross-platform and is used to get the path of special folders such as the home directory of a user or the application data.

  3. Call the handle method of the ipcMain object to start listening for requests in the getContent channel:

    ipcMain.handle('getContent', () => {

      if (fs.existsSync(contentFile)) {

        const result = fs.readFileSync(contentFile);

        return result.toString();

      }

      return '';

    });

    When the main process receives a request in this channel, it uses the existsSync method of the fs library to check whether the file with the content of the editor exists already. If it exists, it reads it using the readFileSync method and returns its content to the renderer process.

  4. Call the handle method again, but this time for the setContent channel:

    ipcMain.handle('setContent', ({}, content: string) => {

      fs.writeFileSync(contentFile, content);

    });

    In the preceding snippet, we use the writeFileSync method of the fs library to write the value of the content property in the file.

  5. Open the package.json file and change the version of the @types/node npm package:

    "@types/node": "^15.6.0"

Now that we have connected the Angular and the Electron application, it is time to preview our WYSIWYG desktop application:

  1. Execute the start:desktop npm script and press F5 to run the application.
  2. Use the editor and its toolbar to enter some content such as the following:
    Figure 5.5 – Editor content

    Figure 5.5 – Editor content

  3. Close the application window and re-run the application. If everything worked correctly, you should see the content that you had entered inside the editor.

Congratulations! You have enriched your WYSIWYG editor by adding persistence capabilities to it. In the following section, we will take the last step toward creating our desktop application, and we will learn how to package it and distribute it.

Packaging a desktop application

Web applications are usually bundled and deployed to a web server that hosts them. On the other hand, desktop applications are bundled and packaged as a single executable file that can be easily distributed. Packaging our WYSIWYG application requires the following steps:

  • Configuring webpack for production mode
  • Using an Electron bundler

We will look at both of them in more detail in the following sections.

Configuring webpack for production

We have already created a webpack configuration file for the development environment. We now need to create a new one for production. Both configuration files will share some functionality, so let's start by creating a common one:

  1. Create a webpack.dev.config.js file in the root folder of the Angular CLI workspace with the following content:

    const path = require('path');

    const baseConfig = require('./webpack.config');

    module.exports = {

      ...baseConfig,

      mode: 'development',

      devtool: 'source-map',

      output: {

        path: path.join(process.cwd(), 'dist', 'my-

          editor'),

        filename: 'shell.js'

      }

    };

  2. Remove the mode, devtool, and output properties from the webpack.config.js file.
  3. Open the package.json file and pass the new webpack development configuration file at the start:desktop script:

    "start:desktop": "concurrently "ng build --delete-output-path=false --watch" "webpack --config webpack.dev.config.js --watch""

  4. Create a webpack.prod.config.js file in the root folder of the Angular CLI workspace with the following content:

    const path = require('path');

    const baseConfig = require('./webpack.config');

    module.exports = {

      ...baseConfig,

      output: {

        path: path.join(process.cwd(), 'dist', 'my-

          editor'),

        filename: 'main.js'

      }

    };

    The main difference with the webpack configuration file for the development environment is that we changed the filename of the output bundle to main.js. The Angular CLI adds a hashed number in the main.js file of the Angular application in production, so there will be no conflicts. Other things to notice are that mode is set to production by default when we omit it, and the devtool property is missing because we do not want to enable source maps in production mode.

  5. Add a new entry in the scripts property of the package.json file for building our application in production mode:

    "scripts": {

      "ng": "ng",

      "start": "ng serve",

      "build": "ng build",

      "watch": "ng build --watch --configuration development",

      "test": "ng test",

      "start:desktop": "concurrently "ng build --delete-

        output-path=false --watch" "webpack --config

          webpack.dev.config.js --watch"",

      "build:electron": "ng build && webpack --config

        webpack.prod.config.js"

    }

    The build:electron script builds the Angular and Electron application in production mode simultaneously.

We have completed all the configurations needed for packaging our desktop application. In the following section, we will learn how to convert it into a single bundle specific to each operating system.

Using an Electron bundler

The Electron framework has a wide variety of tools that are created and maintained by the open source community.

You can see a list of available projects in the Tools section at the following link:

https://www.electronjs.org/community

One of these tools is the electron-packager library, which we can use to package our desktop application as a single executable file for each OS (Windows, Linux, and macOS). Let's see how we can integrate it into our development workflow:

  1. Run the following npm command to install electron-packager as a development dependency to our project:

    npm install -D electron-packager

  2. Add a new entry in the scripts property of the package.json file for packaging our application:

    "scripts": {

        "ng": "ng",

        "start": "ng serve",

        "build": "ng build",

        "watch": "ng build --watch --configuration

          development",

        "test": "ng test",

        "start:desktop": "concurrently "ng build --

          delete-output-path=false --watch" "webpack --

            config webpack.dev.config.js --watch"",

        "build:electron": "ng build && webpack --config

           webpack.prod.config.js",

        "package": "electron-packager dist/my-editor --

          out=dist --asar"

      }

    In the preceding script, electron-packager will read all files in the dist/my-editor folder, package them, and output the final bundle in the dist folder. The --asar option instructs the packager to archive all files in the ASAR format, similar to a ZIP or TAR file.

  3. Create a package.json file in the srcelectron folder and add the following content:

    {

      "name": "my-editor",

      "main": "main.js"

    }

    The electron-packager library requires a package.json file to be present in the output folder and points to the main entry file of the Electron application.

  4. Open the webpack.prod.config.js file and add the CopyWebpackPlugin in the plugins property:

    const path = require('path');

    const baseConfig = require('./webpack.config');

    const CopyWebpackPlugin = require('copy-webpack-

      plugin');

    module.exports = {

      ...baseConfig,

      output: {

        path: path.join(process.cwd(), 'dist', 'my-

          editor'),

        filename: 'main.js'

      },

      plugins: [

        new CopyWebpackPlugin({

          patterns: [

            {

              context: path.join(process.cwd(), 'src',

               'electron'),

              from: 'package.json'

            }

          ]

        })

      ]

    };

    We use the CopyWebpackPlugin to copy the package.json file from the srcelectron folder into the distmy-editor folder while building the application in production mode.

  5. Run the following command to build the application in production mode:

    npm run build:electron

  6. Now run the following npm command to package it:

    npm run package

    The preceding command will package the application for the OS that you are currently running on, which is the default behavior of the electron-packager library. You can alter this behavior by passing additional options, which you will find in the GitHub repository of the library listed in the Further reading section.

  7. Navigate to the dist folder of the Angular CLI workspace. You will find a folder called my-editor-{OS}, where {OS} is your current OS and its architecture. For example, in Windows, it will be my-editor-win32-x64. Open the folder, and you will get the following files:
Figure 5.6 – Application package (Windows)

Figure 5.6 – Application package (Windows)

In the preceding screenshot, the my-editor.exe file is the executable file of our desktop application. Our application code is not included in this file but rather in the app.asar file, which exists in the resources folder.

Run the executable file, and the desktop application should open normally. You can take the whole folder and upload it to a server or distribute it by any other means. Your WYSIWYG editor can now reach many more users, such as those that are offline most of the time. Awesome!

Summary

In this chapter, we built a WYSIWYG editor for the desktop using Angular and Electron. Initially, we created an Angular application and added ngx-wig, a popular Angular WYSIWYG library. Then, we learned how to build an Electron application and implemented a communication mechanism for exchanging data between the Angular application and the Electron application. Finally, we learned how to bundle our application for packaging and getting it ready for distribution.

In the next chapter, we will learn how to build a mobile photo geotagging application with Angular and Ionic.

Practice questions

Let's take a look at a few practice questions:

  1. Which class is responsible for creating a desktop window in Electron?
  2. How do we communicate between the main and renderer processes in Electron?
  3. Which flag enables the use of Node.js in the renderer process?
  4. How do we convert a global JavaScript object into an Angular injectable one?
  5. How do we load Electron in an Angular application?
  6. Which interface do we use for interacting with Electron in an Angular application?
  7. How do we pass data to the main Electron process from an Angular application?
  8. Which package do we use for filesystem manipulation in Electron?
  9. Which library do we use for packaging an Electron application?

Further reading

Here are some links to build upon what we learned in the chapter:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset