Con este tutorial puedes conocer cómo crear una aplicación para Ionic que almacene información en una base de datos "en la nube", de manera que los datos estén siempre disponibles desde cualquier dispositivo con conexión a Internet. Concretamente se va a utilizar el servicio Cloud Firestore, ofrecido por Google dentro de Firebase.

Se va a realizar una aplicación sencilla, que permita almacenar y gestionar una lista de tareas.

Los primeros pasos que se describen a continuación en este tutorial (sólo hasta la inserción de los datos) puedes verlos también en el siguiente vídeo:

Configuración de la base de datos en Google Cloud Firestore

Accede a la consola de Google Cloud Firestore desde la dirección: console.firebase.google.com y crea un nuevo proyecto.

Captura de pantalla 2020 11 25 a las 11.13.10 8a340

Asigna un nombre al proyecto (ten en cuenta que no podrás usar acentos ni algunos caracteres especiales).

Captura de pantalla 2020 11 25 a las 11.14.11 b96a2

En el siguiente paso puedes dejar activado el servicio de Google Analytics o desactivarlo, ya que no va a ser necesario en este tutorial.

Captura de pantalla 2020 11 25 a las 11.18.47 50c22

Una vez finalizada la creación del proyecto, accede a la sección Cloud Firestore del menú izquierdo y usa el botón Crear base de datos.

Captura de pantalla 2020 11 25 a las 10.57.46 9490d

Al iniciarse la creación de la base de datos, se solicita en primer lugar la regla de seguridad. Activa inicialmente la opción de modo de prueba para facilitar la lectura y escritura en la base de datos sin controlar de momento qué usuarios pueden hacerlo (posteriormente se debería establecer un control más exhaustivo).

Captura de pantalla 2020 11 25 a las 11.02.11 d84df

Por defecto se establecen 30 días para el modo de prueba, por lo que si necesitas más tiempo deberás editar posteriormente la fecha que aparece en las reglas.

En la siguiente pantalla deberás establecer la región desde la que usarás la base de datos, por lo que selecciona la zona europea, si es el caso.

Captura de pantalla 2020 11 25 a las 11.08.48 a7999

En la pestaña Datos podrás ir viendo el contenido de la base de datos en cada momento.

Captura de pantalla 2019 02 08 a las 13.45.11 b837c

Creación del nuevo proyecto Ionic

Ejecuta las siguientes sentencias en el terminal para crear un nuevo proyecto en blanco de Ionic, e instalar el módulo de firebase que permite el acceso a la base de datos remota de Google.

ionic start ejemplo-firestore blank
cd ejemplo-firestore 
npm install firebase @angular/fire

Configurar el acceso a la base de datos en el proyecto

Entra en la sección Project Overview y haz clic en el botón </> para obtener la información necesaria para configurar el servicio en una aplicación web o también para Ionic.

Captura de pantalla 2019 02 08 a las 13.51.44 e04fb 3540c

Es necesario de nuevo indicar un nombre (apodo) para tu aplicación.

Captura de pantalla 2020 11 25 a las 11.22.28 b25c9

Y en el siguiente paso aparecerán unos datos de configuración similares a los siguientes:

Captura de pantalla 2020 11 25 a las 11.22.47 05574

Copia los valores de los atributos apiKey, authDomain, ...  dentro del archivo environment.ts como se muestra en el siguiente apartado.

environments/environment.ts

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "????????????",
    authDomain: "????????????",
    databaseURL: "????????????",
    projectId: "????????????",
    storageBucket: "????????????",
    messagingSenderId: "????????????",
appId: "????????????" }
};

 app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, 
    IonicModule.forRoot(), 
    AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFirestoreModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Interface para declarar el tipo de objeto a almacenar en la base de datos

Ejecuta una sentencia similar a la siguiente desde el Terminal para que se genere un archivo donde declararás la estructura de los datos que vas a utilizar. Cambia el nombre "Tarea" por el tipo de objeto que vayas a utilizar en tu proyecto.

ionic generate interface Tarea

tarea.ts

Este es el archivo que generará la sentencia anterior. Indica en él los datos que vas a usar en tu aplicación.

export interface Tarea {
    titulo: string;
    descripcion: string;
} 

Clase para métodos de acceso a la base de datos

Para organizar mejor el código vamos a crear una nueva clase dentro de los servicios del proyecto. Se le va a asignar como nombre "firestore", por lo que con la siguiente orden de la línea de comandos creará el archivo firestore.service.ts dentro de la carpeta app del proyecto.

ionic generate service firestore

Insertar registros

firestore.service.ts

En este archivo (firestore.service.ts) se van a ir declarando los distintos métodos que realicen las operaciones con la base de datos. Comenzamos con el método al que se la ha asignado el nombre insertar, que se encargará de añadir a la base de datos los datos que se indiquen por parámetro.

Este método recibirá 2 parámetros que corresponderán a la colección donde se almacenarán (similar a una tabla de SQL) y los datos en sí.

La inserción de datos en Cloud Firestore se realiza invocando al método add() sobre la colección que obtiene la llamada a angularFirestore.collection(), siendo angularFirestore un objeto de AngularFirestore que se obtiene desde el constructor.

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

import { AngularFirestore } from '@angular/fire/firestore';

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

  constructor(private angularFirestore: AngularFirestore) { 
} public insertar(coleccion, datos) { return this.angularFirestore.collection(coleccion).add(datos); }   }

home.page.html

<ion-header>
  <ion-toolbar>
    <ion-title>
      Ejemplo Firestore
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-item>
    <ion-label>Título</ion-label>
    <ion-input [(ngModel)]="tareaEditando.titulo"></ion-input>
  </ion-item>
  <ion-item>
    <ion-label>Descripción</ion-label>
    <ion-input [(ngModel)]="tareaEditando.descripcion"></ion-input>
  </ion-item>
  <ion-button (click)="clicBotonInsertar()">Añadir tarea</ion-button>
</ion-content>

home.page.ts

import { Component } from '@angular/core';
import { FirestoreService } from '../firestore.service';
import { Tarea } from '../tarea';
@Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], }) export class HomePage { tareaEditando: Tarea; constructor(private firestoreService: FirestoreService) { // Crear una tarea vacía this.tareaEditando = {} as Tarea; } clicBotonInsertar() { this.firestoreService.insertar("tareas", this.tareaEditando).then(() => { console.log('Tarea creada correctamente!'); this.tareaEditando= {} as Tarea; }, (error) => { console.error(error); }); } }

Captura de pantalla 2019 02 15 a las 12.53.55 57b80

Captura de pantalla 2019 02 15 a las 12.56.14 29d92

Obtener lista de registros

firestore.service.ts

  public consultar(coleccion) {
    return this.angularFirestore.collection(coleccion).snapshotChanges();
  }

home.page.ts

  arrayColeccionTareas: any = [{
    id: "",
    data: {} as Tarea
   }];

  constructor(private firestoreService: FirestoreService) {
    this.obtenerListaTareas();
  }

  obtenerListaTareas(){
    this.firestoreService.consultar("tareas").subscribe((resultadoConsultaTareas) => {
      this.arrayColeccionTareas = [];
      resultadoConsultaTareas.forEach((datosTarea: any) => {
        this.arrayColeccionTareas.push({
          id: datosTarea.payload.doc.id,
          data: datosTarea.payload.doc.data()
        });
      })
    });
  }

home.page.html

  <h1>LISTA DE TAREAS</h1>
  <ion-list>
    <ion-item *ngFor="let documentTarea of arrayColeccionTareas">
      <ion-grid>
        <ion-row>
          <h2>{{documentTarea.data.titulo}}</h2>
        </ion-row>
        <ion-row>
          <p>{{documentTarea.data.descripcion}}</p>
        </ion-row>
      </ion-grid>
    </ion-item>
  </ion-list>

Borrado de datos

firestore.service.ts

 public borrar(coleccion, documentId) {
return this.angularFirestore.collection(coleccion).doc(documentId).delete();
}

home.page.ts

  idTareaSelec: string;

  selecTarea(tareaSelec) {
    console.log("Tarea seleccionada: ");
    console.log(tareaSelec);
    this.idTareaSelec = tareaSelec.id;
    this.tareaEditando.titulo = tareaSelec.data.titulo;
    this.tareaEditando.descripcion = tareaSelec.data.descripcion;
  }

  clicBotonBorrar() {
    this.firestoreService.borrar("tareas", this.idTareaSelec).then(() => {
      // Actualizar la lista completa
      this.obtenerListaTareas();
      // Limpiar datos de pantalla
      this.tareaEditando = {} as Tarea;
    })
  }

home.page.html

Añadir a cada item de la lista la llamada al método selecTarea cuando se seleccione un determinado item.

  <ion-list>
    <ion-item *ngFor="let documentTarea of arrayColeccionTareas" (click)="selecTarea(documentTarea)">
      ...
    </ion-item>
  </ion-list>

En este ejemplo se ha añadido un botón para eliminar el item que se encuentre seleccionado: 

  <ion-button (click)="clicBotonBorrar()">Borrar tarea</ion-button>

Modificación de datos

firestore.service.ts

public actualizar(coleccion, documentId, datos) {
return this.angularFirestore.collection(coleccion).doc(documentId).set(datos);
}

home.page.ts

  clicBotonModificar() {
    this.firestoreService.actualizar("tareas", this.idTareaSelec, this.tareaEditando).then(() => {
      // Actualizar la lista completa
      this.obtenerListaTareas();
      // Limpiar datos de pantalla
      this.tareaEditando = {} as Tarea;
    })
  }

home.page.html

El código de este botón debe incluirse junto con la llamada al método selecTarea() que ya se ha mostrado anteriormente al seleccionar un item de la lista.

<ion-button (click)="clicBotonModificar()">Modificar tarea</ion-button>

Consulta de datos a partir de un ID

firestore.service.ts

public consultarPorId(coleccion, documentId) {
  return this.angularFirestore.collection(coleccion).doc(documentId).snapshotChanges();
}

xxxx.page.ts

Lo habitual puede ser que la consulta de los datos de un determinado documento por su ID se haga en un página distinta, por lo que no especifica aquí el nombre de la página donde colocar este código.

Los datos (un documento de FireStore) que se obtengan tras realizar la consulta se deben almacenar en alguna variable con una estructura que permita diferenciar el ID y los datos (DATA) obtenidos. En este ejemplo se va a crear la variable document con los campos id y data (de tipo Tarea):

document: any = {
  id: "",
  data: {} as Tarea
};

En el lugar del código que corresponda (donde se desee realizar la consulta por ID), se incluirán las siguientes líneas que realizan la consulta buscando el ID que se encuentre almacenado en la variable idConsultar (no se incluye en este ejemplo su declaración y asignación de valor, ya que depende del modo de uso que se haga de este tipo de consulta).

this.firestoreService.consultarPorId("tareas", idConsultar).subscribe((resultado) => {
  // Preguntar si se hay encontrado un document con ese ID
  if(resultado.payload.data() != null) {
    this.document.id = resultado.payload.id
    this.document.data = resultado.payload.data();
    // Como ejemplo, mostrar el título de la tarea en consola
    console.log(this.document.data.titulo);
  } else {
    // No se ha encontrado un document con ese ID. Vaciar los datos que hubiera
    this.document.data = {} as Tarea;
  } 
});

xxxx.page.html

Para mostrar algún dato contenido en la consulta se deberá incluir algo como esto (muestra el título de la tarea consultada):

<p> Título: {{ document.data.titulo }} </p>