Angular 2 Tutorial (Parte 6)

Angular 2 Tutorial (Parte 6)

Angular 2 Tutorial

En esta 6ta entrada de la serie Angular 2 Tutorial, vamos a lograr la persistencia de datos combinando IndexedDB con Angular 2.

En la Parte 5, conocimos los que son los Services en Angular 2 y logramos desacoplar del componente (AppComponent) toda lo referente a la gestión de items de la Shopping List.

Sin embargo si refrescamos la pagina en la que se despliega nuestra Shopping List App, veremos que todo el trabajo del usuario se ha perdido. Pues vamos ahora a trabajar en en evitar eso haciendo uso de IndexedDB.

¿Qué es IndexedDB?

Antes de trabajar con IndexedDB viene bien saber qué es verdad?. Pues IndexedDB es un API de bajo nivel a usar del lado del cliente y que permite almacenar cantidades significativas de datos estructurados en formato JSON y asociados a una clave mediante la cual se constituye el indice.

Para trabajar con IndexedDB debemos considerar que la misma es compatible con los siguientes Browsers:

Este sistema de base de datos permite realizar operaciones CRUD de forma totalmente asíncronas por lo que no bloquea el flujo de las aplicaciones y gracias a que la misma mantiene indexados los datos que insertamos, se logra entonces optimizar los tiempos de respuesta durante la búsqueda de datos, de allí el nombre “Indexed”DB 😉

Dentro de una base de datos IndexedDB guardamos los datos en Stores; los Stores son estructuras de datos No relacionadas (NoSQL) que podríamos decir que son como nuestras tablas cuyas columna se definen mediante creación de indices que pueden ser únicos o no.

IndexedDB en Angular 2

Ahora que conocemos IndexedDB veamos como hacer uso de la misma en nuestro proyecto Angular, para ello vamos a realizar los siguientes cambios la clase ListService la cual que define nuestro servicio dentro del fichero app.service.ts.

Primero agregamos las siguientes propiedades las cuales nos servirán para definir nuestra base de datos IndexedDB (prestar atención a los comentarios):

    //Nombre de la BD
    static DATABASE_NAME = 'shoppingListDB';
    //Versión de la Base da datos
    static DATABASE_VERSION = 2;
    //Nombre del Store o Tabla de items
    static ITEMS_TABLE = 'items';
    //Objeto de IndexedDB  
    db;

El siguiente paso es definir un método para crear/inicializar la base de datos IndexedDB el cual llamaremos initIndexedDB y tendrá el siguiente código (prestar atención a los comentarios):

 initIndexedDB(){
        //Obtenemos una referencia del servicio  
        var self = this;
        //Verificamos si el Browser en el que se ejecuta la aplicacion soporta IndexedDB
        if (!window.indexedDB) {
            //En caso de que no soporte lo notificamos con alert al usuario
            window.alert("Su navegador no soporta una versión estable de indexedDB.");
            return;
        } else {
            //En caso de que el Browser si soporte IndexedBD creamos/instaciamos nuestra BD 
            //con el método open pasandole el nombre y la versión de la misma
            var request = window.indexedDB.open(ListService.DATABASE_NAME, ListService.DATABASE_VERSION);
            //Definimos un callback del evemto onupgradeneeded para saber cuando esta lista 
            //la BD para craer el o los Store necesarios     
            request.onupgradeneeded = function(event) {
                self.db = request.result;
                if(self.db != null) {
                    //Llamamos al método para crear el o los Stores
                    self.createItemsStore();
                }
            };
            //Definimos un callback del evento onerror para saber si ha ocurrido algun error y 
            //notificarlo al usario con un alert
            request.onerror = function(event) {
              window.alert("onError" + request.error);
            };
            //Definimos un callback del evento onsuccess para saber que hemos instanciado la BD correctamente 
            request.onsuccess = function(event) {
                self.db = request.result;
                 //Llamamos la método para leer todos los items (Productos y Cantidades) que tenemos almacenados
                self.loadAllItems();
            };
        }
    }

Luego vamos a agregar en el método constructor la llamada al método initIndexedDB(), para lograr lo que es la creación (en la ejecución inicial) y/o instanciamiento de la base de datos (Ejecución inicial y subsiguientes):

constructor() {
  //Inicializamos IndexedDB
  this.initIndexedDB();
}

Si os fijáis, dentro del método initIndexedDB, llamamos a dos métodos más, createItemsStore con el cual creamos el Store de Items en el que se van a almacenar los productos y sus cantidades, este método lo definiremos de la siguiente manera (prestar atención a los comentarios):

createItemsStore() {
  //Creamos el Items Store, el cual funciona como una tabla para almacenar 
  //los datos de proudctos y acntidades
  var objectStore = this.db.createObjectStore(ListService.ITEMS_TABLE, { autoIncrement : true });
  //Definimos un campo o indice para guardar los nombres de los produtos 
  objectStore.createIndex("productName", "productName", { unique: true });
  //Definimos un campo o indice para guardar las cantidades de los produtos
  objectStore.createIndex("cantidad", "cantidad", { unique: false });
  //Implementamos un callback para el evento oncomplete con la finalidad de saber cuando 
  //se ha logrado crear el Store 
  objectStore.transaction.oncomplete = function(event) {
    //Aqui el store ya ha esta creado y listo para ser usado
  }
}

Y el método loadAllItems, con el cual leemos los Items (Productos y cantidades), previamente almacenados y cargamos la propiedad de tipo array llamada items (prestar atención a los comentarios):

loadAllItems() {
    var self = this;
    //Obtenemos una referencia del Store items mediante una transaction al Store items de tipo readonly
    //ya que lo que buscamos es solo leer el contenido del mismo
    var itemObjectStore = self.db.transaction(ListService.ITEMS_TABLE, "readonly").objectStore(ListService.ITEMS_TABLE);
    //Con la referencia del Store item abrimos un cursor para iterar sobre cada uno de los obejto Product 
    //que contiene el Store y lo agreamos a nuetro array de items   
    itemObjectStore.openCursor().onsuccess = function(event) {
      var cursor = event.target.result;
      if (cursor) {
        self.items.push(cursor.value);
        cursor.continue();
      } else {
        //Aquí ya hemos culminado de iterar sobre todos los Items guardado
      }
    };
}

Por último nos queda definir un método que llamaremos addProductToDB, el cual servirá para insertar en el items Store el registro del producto y su cantidad cada vez que el usuario agregue un producto y su cantidad (prestar atención a los comentarios):

    addProductToDB(newProd: Product){
        var self = this;
        //Almacenamos los valores del nuevo producto y su cantidad en el Items Store
        //mediante un objeto transaction al Store items de tipo readwrite
        var itemObjectStore = this.db.transaction(ListService.ITEMS_TABLE, "readwrite").objectStore(ListService.ITEMS_TABLE);
        //Con el objeto del Items Store creamos una peticion de tipo inserción de datos usando el método add del objectStore   
        var req = itemObjectStore.add(newProd);
        //Implementamos un callback para el evento onsuccess para saber cuando hemos agregado con éxito el nuevo objeto Product 
        req.onsuccess = function(event) {
            //Aqui sabemos que se ha agregado con éxito
        };

        //Implementamos un callback para el evento onerror para saber si ha ocurrido algun error durante la inserción del objeto
        //y en caso tal lo notificamos al usaurio con un alert
        req.onerror = function(event) {
            window.alert("addProductToDB onError" + req.error);
        };
    }

Solo nos queda hacer uso del método anterior, el cual llamaremos dentro del método que ya teníamos bajo el nombre addProduct, pro lo que tenemos que modificarlo de la siguiente manera (prestar atención a los comentarios):

  addProduct(newProduct: Product) : Promise<Product[]> {      
        //Creamos un nueva instancia del Product
        let newProd: Product = new Product();
        //Agregamos al nuevo producto, el nombre y la cantidad informada por el usuario
        newProd.productName = newProduct.productName;
        newProd.cantidad = newProduct.cantidad;

        //Agregamos el nuevo item (producto, cantidad) a la IndexedDB
        this.addProductToDB(newProd);
        //Agregamos el nuevo item (producto, cantidad) al array de Items 
        this.items.push(newProd);
         
        //resolvemos la Promise retornando los items activo incluyendo el item agregado
        return Promise.resolve(this.items);
    }

Como se puede ver en el video, después de que agregamos los productos y cantidades refrescamos las página varias veces y aun así mantenemos el listado de productos, esto gracias a que los productos y cantidades los guardamos en la IndexedDB.

Sin embargo, si cerramos el Browser y lo volvemos abrir veremos que se pierden los datos, esto es debido a que nuestra instancia de IndexedDB pertenece a una sesión del browser la cual limpia todos sus datos cuando el mismo es cerrado.

Si quisiéramos mantener los datos a pesar de que se cierre el browser tendríamos que almacenar los mismos en un servidor remoto, pero esto lo dejaremos para una entrega mas adelante. 🙂

Ejecución.

Ahora que tenemos todo lo necesario para implementar IndexedDB en nuestra Shopping List App, vamos a probarlo ejecutando el comando npm start, con lo cual se despliega la aplicación en el Browser, se crea la base de datos IndexedDB del lado del cliente con el respectivo Item Store (todo esto ocurre en el momento en que accedemos por primera vez a la aplicación) y ahora solo nos queda hacer uso de la misma tal como lo mostramos en el siguiente vídeo:

Próximos pasos

Estén atentos a nuestra próxima entrega de la serie Angular 2 Tutorial en la que iremos agregando a nuestra Shopping List App más funcionalidades interesantes; así que no olvides suscribirte a nuestro newsletter para que no te pierdas de ninguna de nuestras próximas publicaciones.

Si te ha gustado este entrada compártela con tus amigos en las redes sociales, y así ayudaras a compartir el conocimiento y si tienes dudas, o inquietudes sobre algún tema no olvides comentar.

Happy Coding!

A %d blogueros les gusta esto: