Hola a todos, hoy os voy a explicar como podemos conectar sqlite con IONIC.
En IONIC podemos usar sqlite que nos permite hacer bases de datos locales, esto en móviles es muy útil, ya que nos permite gestionar de forma fácil y local datos de forma rápida, algo que en un móvil es muy necesario.
Crear proyecto IONIC
Primero, debemos crear nuestro proyecto IONIC, tenemos un tutorial aquí:
Instalar sqlite en IONIC
Después, necesitamos instalar la dependencia sqlite oficial de IONIC, este es el repositorio oficial.
|
1 |
npm install --save @capacitor-community/sqlite |
Te recomiendo instalar @capacitor/preferences y @capacitor/device
Esto no es necesario pero en el fichero capacitor.config.json podemos personalizar la base de datos sqlite:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'com.jeep.app.ionic7.angular.sqlite', appName: 'ionic7-angular-sqlite-starter', webDir: 'www', server: { androidScheme: 'https' }, plugins: { CapacitorSQLite: { iosDatabaseLocation: 'Library/CapacitorDatabase', iosIsEncryption: true, iosKeychainPrefix: 'angular-sqlite-app-starter', iosBiometric: { biometricAuth: false, biometricTitle : "Biometric login for capacitor sqlite" }, androidIsEncryption: true, androidBiometric: { biometricAuth : false, biometricTitle : "Biometric login for capacitor sqlite", biometricSubTitle : "Log in using your biometric" }, electronIsEncryption: true, electronWindowsLocation: "C:\\ProgramData\\CapacitorDatabases", electronMacLocation: "/Volumes/Development_Lacie/Development/Databases", electronLinuxLocation: "Databases" } } }; export default config; |
Ejecutar sql en el navegador
Antes de seguir, para probar en web debemos hacer algunas cosas más, ya que sqlite no se puede ejecutar en un navegador pero si lo podemos adaptar con indexDB.
|
1 |
npm install sql.js |
Vamos a node_modules/sql.js/dist y copiamos el fichero sql-wasm.wasm a nuestra carpeta assets. Esto es necesario, ya que será quien ejecutará las peticiones sql que hagamos en web.
IMPORTANTE
: sino lo copias, más adelante te saldrá un error indicando que lo necesitas.
Preparando sqlite en web con jeep-sqlite
A continuación, debemos instalar jeep-sqlite, que es un componente que nos permite utilizar sqlite en el navegador, solo debe ser ejecutado cuando estemos en modo web.
|
1 |
npm install @jeep-sqlite |
Después, vamos al app.module y añadimos las siguientes lineas antes de ngModule
|
1 2 3 |
import { defineCustomElements as jeepSqlite } from 'jeep-sqlite/loader' jeepSqlite(window) |
Y en el modulo debemos añadir la propiedad schemas:
|
1 2 3 |
schemas: [ CUSTOM_ELEMENTS_SCHEMA ] |
Ese CUSTOM_ELEMENTS_SCHEMA viene de @angular/core
Asi se quedaria:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { HttpClientModule } from '@angular/common/http'; import { defineCustomElements as jeepSqlite } from 'jeep-sqlite/loader' jeepSqlite(window) @NgModule({ declarations: [AppComponent], imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, HttpClientModule], providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], bootstrap: [AppComponent], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] }) export class AppModule {} |
Posteriormente, en el app.component.html, colocaremos el componente jeep-sqlite. Crearemos un par de atributos (load y isWeb).
|
1 |
Seguidamente, en el app.component.ts, necesitaremos indicar cuando estamos en modo web.
Esto lo haremos cuando nuestro dispositivo este listo y podamos acceder a su información usando el plugin Device de Capacitor.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import { Component } from '@angular/core'; import { Device } from '@capacitor/device'; import { Platform } from '@ionic/angular'; import { SqliteService } from './services/sqlite.service'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'], }) export class AppComponent { public isWeb: boolean; public load: boolean; constructor( private platform: Platform, private sqlite: SqliteService) { this.isWeb = false; this.load = false; this.initApp(); } initApp(){ this.platform.ready().then( async () => { // Comprobamos si estamos en web const info = await Device.getInfo(); this.isWeb = info.platform == 'web'; }) } } |
Servicio SQLITE
Ahora, nos crearemos un servicio llamado llamado sqlite.service.ts.
En este servicio, comprobaremos si la base de datos esta creada o si debemos crear la base de datos, indicando desde app.component que la base de datos esta preparada.
Tendremos 4 atributos:
- dbReady: Observable uqe nos indicará si la base de datos esta preparada.
- isWeb: Indica si estamos en modo web o no. Cuando es web debemos almacenarlo de forma diferente.
- isIOS: Indica si estamos usando un IOS o no. Servirá cuando hagamos el CRUD.
- dbName: Indica el nombre de la base de datos que estamos usando.
Tendremos varios métodos:
- init: según la plataforma usada, realizaremos una acción u otra.
- setupDatabase: comprobamos si debemos crear la base de datos o recuperarla.
- downloadDatabase: en el caso de que tengamos que sea la primera vez que tengamos que crear la base de datos, la descargaremos del fichero assets/db.json, este fichero contiene la información de la base de datos.
- getDbName: Obtiene el nombre de la base de datos que estamos usando.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class SqliteService { // Atributos // Observable para comprobar si la base de datos esta lista public dbReady: BehaviorSubject; // Indica si estamos en web public isWeb: boolean; // Indica si estamos en IOS public isIOS: boolean; // Nombre de la base de datos public dbName: string; constructor( private http: HttpClient ) { this.dbReady = new BehaviorSubject(false); this.isWeb = false; this.isIOS = false; this.dbName = ''; } } |
Ejemplo del contenido de db.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "database": "languages.db", "version": 1, "encrypted": false, "mode": "full", "tables": [ { "name": "languages", "schema": [ { "column": "name", "value": "TEXT NOT NULL PRIMARY KEY" } ] } ] } |
Método init del servicio SQLITE
Obtenemos la info del dispositivo y una variable sqlite para almacenar CapacitorSQLite, esto se debe a que tenemos que llamar algunos métodos que no están «oficialmente», esto es probable que lo cambien en un futuro no muy lejano. Al final, comprobaremos si la base de datos se ha iniciado o no. Según el tipo de plataforma, realizaremos diferentes acciones:
-
- android: pedimos permisos al usuario.
- web: en modo web, necesitamos iniciar la webstore.
- IOS: ponemos el atributo isIOS a true, lo usaremos al hacer el CRUD.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
async init() { const info = await Device.getInfo(); // CapacitorSQLite no tiene disponible el metodo requestPermissions pero si existe y es llamable const sqlite = CapacitorSQLite as any; // Si estamos en android, pedimos permiso if (info.platform == 'android') { try { await sqlite.requestPermissions(); } catch (error) { console.error("Esta app necesita permisos para funcionar") } // Si estamos en web, iniciamos la web store } else if (info.platform == 'web') { this.isWeb = true; await sqlite.initWebStore(); } else if (info.platform == 'ios') { this.isIOS = true; } // Arrancamos la base de datos this.setupDatabase(); } |
Método setupDatabase del servicio SQLITE
Comprobamos si hemos creado la base de datos, comprobando el valor de la clave first_setup_key, si no se ha iniciado la base de datos la crearemos, sino simplemente creamos la conexión, la abrimos e indicamos que la base de datos esta lista.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async setupDatabase() { // Obtenemos si ya hemos creado la base de datos const dbSetup = await Preferences.get({ key: 'first_setup_key' }) // Sino la hemos creado, descargamos y creamos la base de datos if (!dbSetup.value) { this.downloadDatabase(); } else { // Nos volvemos a conectar this.dbName = await this.getDbName(); await CapacitorSQLite.createConnection({ database: this.dbName }); await CapacitorSQLite.open({ database: this.dbName }) this.dbReady.next(true); } } |
Método downloadDatabase del servicio SQLITE
Realizaremos el siguiente proceso:
- Obtenemos el fichero db.json.,
- Comprobamos si es valido.
- Si es valido lo importamos.
- Creamos la conexión.
- Abrimos la conexión.
- Indicamos en el valor de la clave first_setup_key a 1 para la próxima vez que iniciemos la app.
- Guardamos el nombre de la base de datos en el valor de la clave dbname.
- Indicamos que la base de datos esta lista.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
downloadDatabase() { // Obtenemos el fichero assets/db/db.json this.http.get('assets/db/db.json').subscribe(async (jsonExport: JsonSQLite) => { const jsonstring = JSON.stringify(jsonExport); // Validamos el objeto const isValid = await CapacitorSQLite.isJsonValid({ jsonstring }); // Si es valido if (isValid.result) { // Obtengo el nombre de la base de datos this.dbName = jsonExport.database; // Lo importo a la base de datos await CapacitorSQLite.importFromJson({ jsonstring }); // Creo y abro una conexion a sqlite await CapacitorSQLite.createConnection({ database: this.dbName }); await CapacitorSQLite.open({ database: this.dbName }) // Marco que ya hemos descargado la base de datos await Preferences.set({ key: 'first_setup_key', value: '1' }) // Guardo el nombre de la base de datos await Preferences.set({ key: 'dbname', value: this.dbName }) // Indico que la base de datos esta lista this.dbReady.next(true); } }) } |
Método getDbName del servicio SQLITE
Obtenemos el nombre de la base de datos desde la clave dbname.
|
1 2 3 4 5 6 7 8 9 10 |
async getDbName() { if (!this.dbName) { const dbname = await Preferences.get({ key: 'dbname' }) if (dbname.value) { this.dbName = dbname.value } } return this.dbName; } |
Ejemplo servicio completo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { CapacitorSQLite } from '@capacitor-community/sqlite'; import { Device } from '@capacitor/device'; import { Preferences } from '@capacitor/preferences'; import { JsonSQLite } from 'jeep-sqlite/dist/types/interfaces/interfaces'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class SqliteService { // Atributos // Observable para comprobar si la base de datos esta lista public dbReady: BehaviorSubject; // Indica si estamos en web public isWeb: boolean; // Nombre de la base de datos public dbName: string; constructor( private http: HttpClient ) { this.dbReady = new BehaviorSubject(false); this.isWeb = false; this.dbName = ''; } async init() { const info = await Device.getInfo(); // CapacitorSQLite no tiene disponible el metodo requestPermissions pero si existe y es llamable const sqlite = CapacitorSQLite as any; // Si estamos en android, pedimos permiso if (info.platform == 'android') { try { await sqlite.requestPermissions(); } catch (error) { console.error("Esta app necesita permisos para funcionar") } // Si estamos en web, iniciamos la web store } else if (info.platform == 'web') { this.isWeb = true; await sqlite.initWebStore(); } else if (info.platform == 'ios') { this.isIOS = true; } // Arrancamos la base de datos this.setupDatabase(); } async setupDatabase() { // Obtenemos si ya hemos creado la base de datos const dbSetup = await Preferences.get({ key: 'first_setup_key' }) // Sino la hemos creado, descargamos y creamos la base de datos if (!dbSetup.value) { this.downloadDatabase(); } else { // Nos volvemos a conectar this.dbName = await this.getDbName(); await CapacitorSQLite.createConnection({ database: this.dbName }); await CapacitorSQLite.open({ database: this.dbName }) this.dbReady.next(true); } } downloadDatabase() { // Obtenemos el fichero assets/db/db.json this.http.get('assets/db/db.json').subscribe(async (jsonExport: JsonSQLite) => { const jsonstring = JSON.stringify(jsonExport); // Validamos el objeto const isValid = await CapacitorSQLite.isJsonValid({ jsonstring }); // Si es valido if (isValid.result) { // Obtengo el nombre de la base de datos this.dbName = jsonExport.database; // Lo importo a la base de datos await CapacitorSQLite.importFromJson({ jsonstring }); // Creo y abro una conexion a sqlite await CapacitorSQLite.createConnection({ database: this.dbName }); await CapacitorSQLite.open({ database: this.dbName }) // Marco que ya hemos descargado la base de datos await Preferences.set({ key: 'first_setup_key', value: '1' }) // Guardo el nombre de la base de datos await Preferences.set({ key: 'dbname', value: this.dbName }) // Indico que la base de datos esta lista this.dbReady.next(true); } }) } async getDbName() { if (!this.dbName) { const dbname = await Preferences.get({ key: 'dbname' }) if (dbname.value) { this.dbName = dbname.value } } return this.dbName; } } |
Volvemos al app.component.ts y estaremos pendiente de cuando esta la base de datos preparada.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import { Component } from '@angular/core'; import { Device } from '@capacitor/device'; import { Platform } from '@ionic/angular'; import { SqliteService } from './services/sqlite.service'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'], }) export class AppComponent { public isWeb: boolean; public load: boolean; constructor( private platform: Platform, private sqlite: SqliteService) { this.isWeb = false; this.load = false; this.initApp(); } initApp(){ this.platform.ready().then( async () => { // Comprobamos si estamos en web const info = await Device.getInfo(); this.isWeb = info.platform == 'web'; // Iniciamos la base de datos this.sqlite.init(); // Esperamos a que la base de datos este lista this.sqlite.dbReady.subscribe(load => { this.load = load; }) }) } } |
Aquí os dejo el repositorio de Github.
Os dejo un video donde explicamos como conectar sqlite en IONIC, hasta el minuto 21:36.
Espero que os sea de ayuda. Si tenéis dudas, preguntad. Estamos para ayudarte.
Hola, excelente todo el material, me esta sirviendo bastante, pero tengo una duda con respecto al json de Sqlite, ¿cómo puedo colocar las constraints?.
No encuentro aún la manera.
Hola, estoy tratando de seguir el tutorial para agregar sqlite pero el ionic 7.2 no puedo encontar «app.module.ts» , podras indicar como se puede hacer con esta nueva version de ionic. desde ya muchas gracias
hola tengo el proyecto realizado en ionic 7.2 usando react, pero no puedo seguir el tutorial ya que esta hecho para angular, quye tendria que modificar para que funcione. gracias
El tutorial esta en angular, en react no hay app.module.
echale un ojo a este repositorio
https://github.com/jepiqueau/react-sqlite-hook