Cómo crear una aplicación de Electron usando Angular y SQLite3.

Recientemente estuve experimentando con la conversión de una de mis aplicaciones web de Angular en una aplicación de escritorio usando Electron. Encontré algunos obstáculos en el camino y decidí poner mi experiencia por escrito para que pueda ayudar a otros. Si tiene planes similares para su proyecto, espero que esto le sea de utilidad. El código fuente de esta guía se puede encontrar aquí.

Parte I: Angular

Crea el Boilerplate.

Por el bien de esta guía, crearemos una nueva aplicación Angular desde cero. Usaré Electron-Forge para crear el texto estándar. Electron-Forge ofrece varias plantillas para crear código repetitivo, incluida una para Angular 2. Primero instale la CLI de Electron-Forge.

$ npm i -g electron-forge

Ahora use la CLI para crear el texto estándar de la aplicación Angular.

$ electron-forge init electron-angular-sqlite3 --template=angular2$ cd electron-angular-sqlite3

La CLI de Forge agregará lo esencial que necesitamos para ejecutar nuestra aplicación. Agreguemos algunos directorios adicionales para albergar nuestros archivos de base de datos. Agregue un directorio de activos en src y coloque los directorios de datos y modelos debajo de él.

$ mkdir ./src/assets/data ./src/assets/model

El árbol de directorios ahora debería verse así:

.+-node_modules+-src| || +-assets| | || | +-data| | +-model| || +-app.component.ts| +-bootstrap.ts| +-index.html| +-index.ts|+-.compilerc+-.gitignore+-package-lock.json+-package.json+-tsconfig.json+-tslint.json

Escribe algún código.

Como primer paso, agreguemos un archivo de modelo que coincidirá con el esquema de nuestra base de datos. Para este ejemplo simple, creemos una clase llamada Item. Cada artículo contendrá un id y una propiedad de nombre. Guarde el archivo en su proyecto en src/assets/model/item.schema.ts.

Usaremos TypeORM para nuestro mapeo relacional de objetos. Primero instale TypeORM.

$ npm install typeorm --save

Seguiremos la guía TypeORM para crear esquemas aquí. Cuando termine, el archivo debería verse así:

TypeORM hace uso de decoradores mecanografiados. Usamos el decorador Entity para declarar nuestra clase Item como una tabla. El @PrimaryGeneratedColumn()decorador declara idcomo nuestra identificación única y le dice a la base de datos que la genere automáticamente. Nos preocuparemos de vincularnos a una base de datos más adelante.

Crea el servicio.

Nuestra próxima acción probable sería crear un servicio de aplicaciones que maneje la comunicación desde el principio hasta el final. Electron pone a disposición la IpcRendererclase solo para esto. IpcRendereres la clase de comunicación entre procesos de Electron que se utiliza en el proceso de renderizado. Básicamente, queremos usar IpcRendererpara enviar mensajes al proceso principal de Electron. Estos mensajes pasarán información al proceso principal para que pueda manejar las interacciones de la base de datos.

Implementar el IpcRendereres donde nos encontramos con nuestro primer obstáculo. Electron se basa en el método window.require (), que solo está disponible dentro del proceso de renderizado de Electron. Este es un problema bien documentado. Para solucionar esto, podemos usar el paquete ngx-electron de ThornstonHans, que agrupa todas las API de Electron expuestas al proceso de renderizado en un solo Servicio Electron. Puedes leer más sobre esto aquí.

Antes de que podamos usarlo ngx-electron, necesitamos instalarlo.

$ npm install ngx-electron --save

Ahora creemos un servicio para manejar nuestra IpcRenderercomunicación. Crear src/app.service.ts.

En app.service.tscreamos una clase llamada AppServicey agregamos el @Injectable()decorador. Esto nos permite usar la inyección de dependencia (DI) incorporada de angular. En nuestro constructor, creamos una variable local _electronServicede tipo ElectronService. La ElectronServiceclase nos la proporciona ngrx-electron. Nos permite usar la IpcRenderclase de Electron sin ninguno de los problemas antes mencionados.

Creamos tres funciones: una que obtiene todos los elementos de la base de datos, otra para agregar un elemento a la base de datos y otra para eliminar un elemento. Cada función devolverá un Observable.

Los observables son parte de la biblioteca RxJs y proporcionan una buena forma de manejar nuestras interacciones con la base de datos de forma asincrónica. Puedes leer más sobre Observables aquí. Tenga en cuenta el uso del operador Observable ofpara indicar que estamos envolviendo nuestra respuesta this._electronService.ipcRenderer.sendSync()como un valor Observable.

Registro de servicios y componente de escritura.

Con nuestro servicio completo, entremos src/app.component.tsy regístrelo para DI. Mientras esté allí, agregaremos una plantilla html simple y funciones para manejar nuestros eventos de botón.

Asegúrese de agregar AppServicecomo proveedor en los @NgModuleargumentos del decorador y también como variable privada en el AppComponentconstructor. También necesitamos agregar ElectronServicecomo proveedor.

Al inicializar nuestro componente, queremos cargar todo el contenido de nuestra base de datos y mostrarlo. Para ello, nos suscribimos a la addItem()función del servicio que creamos. Si recuerda, todas nuestras funciones de servicio devuelven Observables. Para obtener datos de nuestro observable, nos suscribimos a él, pasando una función de devolución de llamada que se ejecuta cuando se reciben los datos. En el ejemplo anterior, (items) => (this.itemList = items) completará nuestra variab le itemList de clase con el contenido de la base de datos una vez que se recupere.

Seguimos tácticas similares para agregar y eliminar elementos de la base de datos. Cada vez que se repobla itemListcon los contenidos actualizados de la base de datos.

Parte II: Electrón

Instalación de SQLite3.

Ahora que terminamos nuestro front-end, necesitamos crear el backend de Electron. El backend de Electron manejará y procesará los mensajes enviados desde el frente y administrará la base de datos sqlite3.

Usaremos sqlite3 para nuestra base de datos y necesitaremos instalarlo.

$ npm install sqlite3 --save

Un obstáculo que encontré mientras trabajaba con sqlite3 y Electron inicialmente, fue que los binarios nativos de sqlite deben recompilarse para su uso con Electron. Electron-Forge debería encargarse de esto por usted. Una cosa a tener en cuenta, Electron-Forge utilizará node-gyp para compilar los binarios. Es posible que deba tenerlo instalado y configurado correctamente antes de usarlo, lo que incluye la instalación de Python. A partir de ahora, node-gyp usa python 2. Si tiene varias versiones en su máquina, debe asegurarse de que la compilación actual esté usando la correcta.

Conexión a la base de datos.

Now let’s open our src/index.ts file and add some code to connect to the database. The two things we need to do are, connect to the database, and add functions to handle our requests from the renderer process. The finished file looks like this:

An in depth explanation of TypeORM and Electron is beyond the scope of this

guide, so I will only briefly discuss the above file. First we need to import the createConnection class from the TypeORM library. We also need to import or Item schema.

As expected, the createConnection class will create a connection to our database. We pass it a constructor with parameters such as type, database, and entities. Type is a string that describes what type of database we are using. Database is a string that points to the database location. Entities is where we tell TypeORM what schemas to expect. For our purpose: type is ‘sqlite’, Database is ‘./src/assets/data/database.sqlite’, and Entities is our imported Item class.

TypeORM allows you two options when working with database transactions: EntityManager and Repository. Both will give you access to functions for querying the database, without writing the SQL. We create a Repository object with the line itemRepo = connection.getRepository(Item) . This gives us access to transaction methods for our Item table.

The last step is to create functions to handle the messages being sent from the IpcRenderer. Each function will use the itemRepo object we created to access the database. After successful completion of each transaction, the functions will pass the new state of the database back to the renderer.

Part III: Run it!

With everything complete, we can now run the app. Electron-Forge handles this process for us. All we need to do is run the command:

$ npm run start

If everything is correct, Electron will open your app and you can test it out.

Thanks for reading!