1

I'm creating a desktop application with electron and angular7, which persists data in a sqlit3 database. To perform CRUD operations in the angular application I had to implement a communication between electron's render- and main-process. While this works, the problem is though, that I had to add new event listeners for every single database operation I wanted to perform.

This boils down to inflating my main.ts file, where I create the browser window. It just feels very wrong to write basically write the same code over and over again and more or less only changing the event name. I followed some tutorials for this, since I'm quite new to electron and also angular, this tutorial in particular (also I didn't use electron-forge):

How to create an Electron app using Angular and SQLite3

...and ended up following this advise to make nodes sqlite3 module work:

How to use sqlite3 module with electron?

After implementing the basic stuff the createWindow function in my main.ts file looks like the following:

const createWindow = async () => {

  const connection = await createConnection({
    type: 'sqlite',
    synchronize: true,
    logging: true,
    logger: 'simple-console',
    database: './data/database.db',
    entities: [Item],
  });

  const itemRepo = connection.getRepository(Item);

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

  win.loadURL(url.format({
      pathname: path.join(__dirname, `/../../dist/angular-electron-sqlite/index.html`),
      protocol: 'file:',
      slashes: true
    })
  );

  win.webContents.openDevTools();

  win.on('closed', () => {
    win = null;
  });

  ipcMain.on('get-items', async (event: any, ...args: any[]) => {
    try {
      event.returnValue = await itemRepo.find();
    } catch (err) {
      throw err;
    }
  });

  ipcMain.on('add-item', async (event: any, _item: Item) => {
    try {
      const item = await itemRepo.create(_item);
      await itemRepo.save(item);
      event.returnValue = await itemRepo.find();
    } catch (err) {
      throw err;
    }
  });

  ipcMain.on('delete-item', async (event: any, _item: Item) => {
    try {
      const item = await itemRepo.create(_item);
      await itemRepo.remove(item);
      event.returnValue = await itemRepo.find();
    } catch (err) {
      throw err;
    }
  });
};

It doesn't look too bad yet, but I also only added operations for one table so far. If I got the concept right, I understand that I have to add new listeners for every new table I want to use in my application. I am looking for a way to either add event listeners in more dynamic way or a different and better strategy to connect a SQLite3 database. At least a way to move the whole registering event listeners to different files.

MaikB
  • 115
  • 7

1 Answers1

0

After a while, I started to find a solution by adding this code to my createWindow() function:

ipcMain.on('find', async (event, ...args) => {
  try {
    event.returnValue = await connection.getRepository(args[0]).find();
  } catch (err) {
    throw err;
  }
});

// Emitted to find one entry in the SQLite database.
ipcMain.on('findOne', async (event, ...args) => {
  try {
    event.returnValue = await connection.getRepository(args[0]).findOne(args[1]);
  } catch (err) {
    throw err;
  }
});

// Emitted to save/update an entry in the SQLite database.
ipcMain.on('save', async (event, ...args) => {
  try {
    event.returnValue = await connection.getRepository(args[0]).save(args[1]);
  } catch (err) {
    throw err;
  }
});

// Emitted to remove an entry from the SQLite database.
ipcMain.on('remove', async (event, ...args) => {
  try {
    event.returnValue = await connection.getRepository(args[0]).remove(args[1]);
  } catch (err) {
    throw err;
  }
});

and by creating a DatabaseService in my Angular app:

import { Injectable } from '@angular/core';
import { plainToClass } from 'class-transformer';
import { ClassType } from 'class-transformer/ClassTransformer';
import { ElectronService } from 'ngx-electron';

@Injectable({
  providedIn: 'root'
})
export class DatabaseService {

  constructor(
    private electronService: ElectronService
  ) { }

  find<Entity>(from: ClassType<Entity>): Promise<Entity[]> {
    return new Promise<Entity[]>(resolve => {
      try {
        resolve(this.electronService.ipcRenderer.sendSync('find', from.name).map((entity: any) => {
          return plainToClass(from, entity);
        }));
      } catch (err) {
        console.log(err);
      }
    });
  }

  findOne<Entity>(from: ClassType<Entity>, id: number): Promise<Entity> {
    return new Promise<Entity>(resolve => {
      try {
        resolve(plainToClass(from, this.electronService.ipcRenderer.sendSync('findOne', from.name, id)));
      } catch (err) {
        console.log(err);
      }
    });
  }

  save<Entity>(to: ClassType<Entity>, entity: Entity): Promise<Entity> {
    return new Promise<Entity>(resolve => {
      try {
        resolve(plainToClass(to, this.electronService.ipcRenderer.sendSync('save', to.name, entity)));
      } catch (err) {
        console.log(err);
      }
    });
  }

  remove<Entity>(from: ClassType<Entity>, entity: Entity): Promise<Entity> {
    return new Promise<Entity>(resolve => {
      try {
        resolve(plainToClass(from, this.electronService.ipcRenderer.sendSync('remove', from.name, entity)));
      } catch (err) {
        console.log(err);
      }
    });
  }

}

Unfortunately, the Entity template is not forwarded to the plainToClass() function in order to cast plain JSON to the real entity. I don't know if template forwarding is available in TypeScript so maybe someone could complete this solution....

didil
  • 693
  • 8
  • 22