Auto-guardado local, no vuelvas a perder nada

Imagínate esta situación: estás trabajando en un informe importante en un editor en línea y, de repente, ¡la conexión a Internet se cae! ¿El resultado? Podrías perder todo tu trabajo. Para evitar este dolor de cabeza digital, el guardado local se convierte en tu mejor amigo. Así que, en un tono amigable y serio, exploremos esta característica y cómo funciona en un editor de informes en Angular.

El Guardado Local: Tu Compañero de Confianza

En este mundo de interrupciones y problemas técnicos constantes, el guardado local es como un héroe digital. Es una función que te permite guardar una copia de tu trabajo en tu dispositivo, asegurando que no se pierda en las nubes de Internet. El código que te voy a mostrar te enseña cómo implementar el guardado local en una aplicación Angular, especialmente en un editor de informes. ¿Las ventajas? Son muchas y muy valiosas.

1. Protección contra la Pérdida de Datos

Nada es más desalentador que perder horas de trabajo debido a problemas de conexión. Con el guardado local, esta pesadilla se desvanece. El código que vamos a ver se encarga de guardar automáticamente el informe en tu dispositivo, garantizando que tus datos estén a salvo, incluso cuando la tormenta digital hace de las suyas.

2. Flexibilidad y Continuidad

El guardado local también te da la libertad que necesitas. ¿Necesitas seguir trabajando en tu informe en un vuelo o en un lugar con una conexión limitada? No hay problema. Con el guardado local, puedes seguir trabajando sin problemas, sin importar dónde estés. Esto te asegura que tu trabajo siga avanzando sin contratiempos.

3. Protección contra Errores Humanos

Quién no ha borrado accidentalmente una sección importante de un informe o ha sobrescrito información crucial. Con el guardado local, los errores humanos son menos aterradores. El sistema te permite restaurar versiones anteriores de tu trabajo, lo que actúa como un salvavidas cuando cometes un error. Así, se reducen las situaciones de estrés y desesperación.

4. Ahorro de Tiempo y Estrés

La amenaza constante de perder datos puede ser estresante y llevar a perder tiempo valioso. El guardado local, en cambio, te ofrece tranquilidad al asegurarte de que tus datos estén respaldados en tu propio dispositivo. Esto ahorra tiempo y esfuerzo, algo realmente valioso en entornos profesionales y personales.

El Código en Acción: Implementación en Angular

Ahora que hemos hablado de los beneficios del guardado local, veamos cómo funciona en una aplicación Angular. En el componente «AutosaveComponent,» el código se encarga de guardar automáticamente el informe en tu dispositivo y de recuperar versiones anteriores en caso de problemas. A continuación, te muestro el código completo y una breve explicación de cómo funciona:

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import * as moment from 'moment';
import { AuthUserService } from 'projects/cris9/src/app/services/authUser.service';

@Component({
  selector: "app-autosave",
  templateUrl: "./autosave.component.html",
  styleUrls: ["./autosave.component.css"],
})
export class AutosaveComponent implements OnInit {
  @Input()
  set report(value: string) {
    this._report = {report: value, TTL: moment().format("DD/MM/yyyy HH:mm:ss") };
  }

@Input() set report(value: string): Aquí definimos un «setter» para la entrada «report» que nos permite capturar el valor del informe y su marca de tiempo actual. Esto nos ayudará a mantener un registro de los cambios.

@Input() set AN(value: string) { this._AN = value; }

@Input() set AN(value: string): Similar al anterior, este «setter» captura el valor de «AN,» que es parte del contexto del informe.

@Output() restoreTemplate = new EventEmitter<string>();

@Output() restoreTemplate: Esta línea define un evento de salida que nos permitirá restaurar una versión anterior del informe si es necesario.

constructor(private _authUser: AuthUserService) {}

constructor(private _authUser: AuthUserService): Aquí estamos inyectando el servicio «_authUser» para poder utilizarlo en nuestras funciones.

  lastTime;
  lastResult: boolean;
  saving: boolean;
  notSavedYet;
  private _report;
  private _AN;
  private _refreshInterval;

Variables: Estamos declarando varias variables que se usarán en todo el componente para realizar un seguimiento del estado del guardado y la información del informe.

  ngOnInit(): void {
    // Verificar si existe un informe anterior
    if (this.checkExist()) {
      // Iniciar temporizador
      this._refreshInterval = setInterval(() => {
        this.autosave();
      }, 15000);
    }
  }

ngOnInit(): Esta es una función del ciclo de vida de Angular. Aquí comenzamos por verificar si existe un informe anterior (la función checkExist lo hará por nosotros). Si hay un informe anterior, iniciamos un temporizador para el guardado automático con un intervalo de 15 segundos (15000 milisegundos).

  checkExist(): Boolean {
    try {
      if (
        localStorage.getItem(this._authUser.getUser() + "-" + this._AN) !=
          null &&
        localStorage.getItem(
          this._authUser.getUser() + "-" + this._AN + "-" + "lastDate"
        ) != null
      ) {

checkExist(): Esta función verifica si existen datos previamente guardados. Revisamos si hay una entrada en el almacenamiento local para el usuario y para «AN» (parte del contexto del informe), así como la marca de tiempo de la última modificación.

        if (
          (JSON.stringify(JSON.parse(localStorage.getItem(this._authUser.getUser() + "-" + this._AN)).report) != JSON.stringify(this._report.report)) &&
          confirm(
            "Se ha encontrado una versión local del informe con fecha " +
              localStorage.getItem(
                this._authUser.getUser() + "-" + this._AN + "-" + "lastDate"
              ) + " que no ha sido guardada en el servidor." +
              "\n¿Quieres cargarla?\n\n" +
              "** Si no cargas el informe recuperado, se descartará y se sobrescribirá **"
          )
        ) {

Continuación de checkExist(): Aquí, comparamos el informe actual con el informe guardado localmente para ver si ha habido cambios. Si se detectan cambios, mostramos un mensaje de confirmación para cargar la versión local. Si el usuario confirma, recuperamos el informe local y actualizamos el estado.

          this._report = JSON.parse(
            localStorage.getItem(this._authUser.getUser() + "-" + this._AN)
          );
          this.lastTime = localStorage.getItem(
            this._authUser.getUser() + "-" + this._AN + "-" + "lastDate"
          );
          this.lastResult = true;
          this.notSavedYet = false;
          this.restoreTemplate.emit(JSON.stringify(this._report.report));
        }
      } else {
        localStorage.removeItem(this._authUser.getUser() + "-" + this._AN);
        localStorage.removeItem(
          this._authUser.getUser() + "-" + this._AN + "-" + "lastDate"
        );
        this.notSavedYet = true;
      }

Continuación de checkExist(): Si el usuario decide cargar la versión local, aquí actualizamos el estado del informe y marcamos que no se ha guardado aún en el servidor. Si no hay datos locales, eliminamos cualquier información antigua y marcamos que aún no se ha guardado.

      return true;
    } catch (exception) {
      console.log(exception);
      alert("Lo sentimos, se ha producido un error al recuperar el informe :(");
      localStorage.removeItem(this._authUser.getUser() + "-" + this._AN);
      localStorage.removeItem(
        this._authUser.getUser() + "-" + this._AN + "-" + "lastDate"
      );
      this.notSavedYet = true;
      return true;
    }
  }

Continuación de checkExist(): En caso de que ocurra algún error durante la verificación, manejamos la excepción y mostramos un mensaje de alerta. Luego, limpiamos los datos locales y marcamos que no se ha guardado.

autosave() {
    try {
      this.saving = true;
      localStorage.setItem(
        this._authUser.getUser() + "-" + this._AN,
           JSON.stringify(this._report));
      localStorage.setItem(
        this._authUser.getUser() + "-" + this._AN + "-" + "lastDate",
        moment().format("DD/MM/yyyy HH:mm:ss")
      );
      this.lastTime = moment().format("DD/MM/yyyy HH:mm:ss");
      this.lastResult = true;
      this.saving = false;
      this.notSavedYet = false;
    } catch (exception) {
      this.lastTime = moment().format("DD/MM/yyyy HH:mm:ss");
      this.lastResult = false;
      this sving = false;
      this.notSavedYet = false;
    }
  }
}

autosave(): Esta función se encarga de guardar automáticamente el informe en el dispositivo del usuario. Primero, marcamos que estamos en proceso de guardado y luego almacenamos el informe y la marca de tiempo en el almacenamiento local. Si todo va bien, actualizamos el estado y marcamos que no se ha guardado en el servidor. Si ocurre un error, manejamos la excepción y marcamos que no se ha guardado.

Conclusión

En resumen, el guardado local es una función esencial para garantizar la seguridad y la continuidad en la creación de informes en línea. Protege contra la pérdida de datos, ofrece flexibilidad, previene errores humanos y ahorra tiempo. La implementación de esta característica, como se muestra en el código de Angular, es una excelente decisión para crear un editor en línea más amigable y eficiente.

Nuestra intención es ayudarte a que tu experiencia en la creación de informes sea lo más agradable y sin preocupaciones posible. Con el guardado local, puedes estar seguro de que tu trabajo está en buenas manos, incluso cuando las nubes digitales amenazan con tormenta.

Cómo Filtrar y Gestionar Datos del Local Storage en JavaScript

En este artículo, exploraremos cómo filtrar y gestionar datos almacenados en el Local Storage de tu navegador web utilizando un script JavaScript. Descubre cómo aplicar expresiones regulares para seleccionar datos específicos y cómo calcular el tamaño del Local Storage. ¡Optimiza tus proyectos web y ofrece a tus usuarios una experiencia más eficiente!

Introducción

Aprenderemos a utilizar un script JavaScript para filtrar datos en el Local Storage y calcular su tamaño. Estos conocimientos son esenciales para el desarrollo de aplicaciones web y pueden marcar la diferencia en la experiencia del usuario. Expliquemos el funcionamiento de este script de manera accesible y amigable.

Filtrando Datos del Local Storage

El primer script que vamos a explorar se utiliza para filtrar datos específicos en el Local Storage. Aquí está el código:

const re = /[a-zA-Z]/g;

Object.keys(localStorage).forEach(function (key) {
  if (key.match(re)) {
    // Realizar alguna acción
  }
});

Vamos a analizarlo paso a paso para comprenderlo a fondo:

  1. const re = /[a-zA-Z]/g;: En esta línea, definimos una expresión regular que busca letras en mayúsculas y minúsculas. Este enfoque es fundamental para seleccionar datos específicos en el Local Storage.
  2. Object.keys(localStorage).forEach(function (key) { ... });: Aquí, obtenemos las claves almacenadas en el Local Storage y las recorremos usando un bucle forEach. Es un paso importante para acceder a los datos que deseamos filtrar.
  3. if (key.match(re)) { ... }: En cada iteración, comprobamos si la clave actual coincide con nuestra expresión regular utilizando el método match(). Si la clave contiene al menos una letra, ejecutamos un código personalizado dentro del bloque if.

Calculando el Tamaño del Local Storage

Añadiremos otro script que calcula el tamaño total del Local Storage y lo muestra en kilobytes (KB) o megabytes (MB). Aquí tienes el código:

var allStrings = '';
for (var key in window.localStorage) {
  if (window.localStorage.hasOwnProperty(key)) {
    allStrings += window.localStorage[key];
  }
}

let size = 3 + (allStrings.length * 16) / (8 * 1024);

if (size > 1024) {
  this.localStorageSize = Math.ceil(size / 1024) + ' MB';
} else {
  this.localStorageSize = allStrings
    ? Math.ceil(3 + (allStrings.length * 16) / (8 * 1024)) + ' KB'
    : '0 KB';
}

Este script recorre todas las claves del Local Storage, recopila sus valores y calcula el tamaño total en KB o MB. Esto es útil para monitorear y gestionar el espacio utilizado por tus datos almacenados en el navegador.

Uso y Personalización

Puedes personalizar estos scripts según tus necesidades. Además de realizar una «acción» en el primer script, ahora también puedes mostrar el tamaño total del Local Storage a tus usuarios. Esto es útil para mantener un control sobre el espacio de almacenamiento y tomar decisiones informadas.

Hacer compatible Angular 9 con Internet Explorer

En este mini-tutorial vamos a ver que pasos tienes que hacer para que tu proyecto Angular 9 (también es compatible con Angular 8) compilado funcione correctamente en todos los navegado, incluido el odioso Internet Explorar.

Paso 1: Polyfills.ts

En el fichero polyfills.ts, descomenta estas dos líneas:

  import 'classlist.js';
  import 'web-animations-js';

Una vez descomentadas, necesitaremos instalar los paquetes de classlist y web-animations-js. Así abriremos un terminal y navegaremos hasta el directorio de nuestro proyecto. Una vez allí, ejecutamos:

npm install --save classlist.js
npm install --save web-animations-js

En el fichero polyfills.ts, asegurate de tener importado zone.js
Se supone que viene por defecto con angular-cli, pero algunos me han reportado problemas y era debido a esto.

import 'zone.js/dist/zone';  // Included with Angular CLI.

Paso 2: tsconfig

Vamos a crear un acrchivo en la raiz de nuestro entorno de trabajo (junto al tsconfig.json) llamado «tsconfig.es5.json» con el siguiente contenido

{
  "extends": "./tsconfig.app.json",
  "compilerOptions": {
      "target": "es5"
  }
}

Paso 3: angular.json

En el fichero angular.json, vamos a agregar una nueva configuración a nuestro proyecto. En las secciones «build» y «serve», necesitamos agregar una nueva configuración «es5», para especificar el nuevo tsconfig creado, y su uso en el comando serve.

// sección build > configurations > production
            "es5": {
              "tsConfig": "./tsconfig.es5.json"
            }

// Sección serve:
            "es5": {
              "browserTarget": ":build:es5"
            }

Nuestro angular.json quedaría así en las secciones build y serve

"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": false,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            },
            "es5": {
              "tsConfig": "./tsconfig.es5.json"
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": ":build"
          },
          "configurations": {
            "production": {
              "browserTarget": ":build:production"
            },
            "es5": {
              "browserTarget": ":build:es5"
            }
          }
        },

En <nombre-de-tu-app> debes poner el nombre de tu aplicación y (como es lógico, en las secciones style y scripts debes tener importado todo aquello que necesites.

Paso 4: browserslist

En el fichero browserslist, quitar el not de la línea not IE 9-11.

# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries

# You can see what browsers were selected by your queries by running:
#   npx browserslist

> 0.5%
last 2 versions
Firefox ESR
not dead
// not IE 9-11 # For IE 9-11 support, remove 'not'.
IE 9-11 # For IE 9-11 support, remove 'not'.

Paso 5: index.html

En el fichero index.html, añadiremos dentro del <head>:


All is done!

Con esto ya tendremos nuestro proyecto compatible. pero hay un par de detalles que debes tener en cuenta:

  • Si lanzas un «ng serve» sin parametros, tu proyecto seguirá siendo compatible sólo con chrome (yo lo uso así para desarrollar)
  • Si quieres lanzar un «ng serve» compatible con IE, debes hacerlo con el parámetro «–configuracion es5» para que cargue la configuración y quedaría así:

ng serve --configuration es5

  • Una vez conpiles con –prod para publicar tu proyecto, se realizarán dos compilaciones, una para es2015 y otra para es5 dando como resultado esta lista de ficheros (este es un ejemplo de un proyecto real donte tb hay agregados primeng y fontawesome)
      assets
38024 3rdpartylicenses.txt
370756 5-es2015.e783c2997b5e11df7397.js
372417 5-es5.e783c2997b5e11df7397.js
10355 color.6441e63a57ccc5105bad.png
132728 fa-brands-400.1437eef49e3db661f92c.ttf
133034 fa-brands-400.3825c40fd00d9bbb76b7.eot
715890 fa-brands-400.55faa7ef298ebafcc322.svg
76548 fa-brands-400.9a01a31d6767f82529dc.woff2
89824 fa-brands-400.e1c1a88e6228f902435e.woff
13600 fa-regular-400.5fab4ed5c3745c12c7e1.woff2
34092 fa-regular-400.814c2c571f030686e71c.ttf
16800 fa-regular-400.acdccc059cdf7b4063e9.woff
34390 fa-regular-400.eb99d5076e8ce45ccfa1.eot
144322 fa-regular-400.fa5f132deb163050a148.svg
76120 fa-solid-900.0f27e9b933cc50abbbba.woff2
193792 fa-solid-900.8618686494a5c8092120.ttf
194078 fa-solid-900.a0b3c6d0d774520787d8.eot
849240 fa-solid-900.a84653d4fe0072d182b6.svg
99004 fa-solid-900.afbdcbccd6861d9cdc38.woff
168641 favicon.ico
  293 hue.f8505bd4d6f3e3aa435b.png
 1938 index.html
13112 line.39c65dcc08f7edb347b6.gif
 9427 loading.9347db5956a89b0bab38.gif
897689 main-es2015.cec9e5f30730177c99be.js
1004458 main-es5.cec9e5f30730177c99be.js
27604 open-sans-v15-latin-300.252a41f69d4c320154a1.ttf
15545 open-sans-v15-latin-300.2aeabc9e8ed0aef227d7.eot
55181 open-sans-v15-latin-300.bb3b405a4608d51ddb13.svg
18280 open-sans-v15-latin-300.df17d4e30091735253a8.woff
14564 open-sans-v15-latin-300.f2ebdee8c2343f558a02.woff2
18476 open-sans-v15-latin-700.2509c35b0cfc629f81e5.woff
14720 open-sans-v15-latin-700.7435e6c2064f36f48626.woff2
15667 open-sans-v15-latin-700.75e0e55bdb3e82dcacf8.eot
55652 open-sans-v15-latin-700.926388f043fc8117b35b.svg
28192 open-sans-v15-latin-700.ec32d6be7329ece0789c.ttf
56266 open-sans-v15-latin-regular.1d0d8b66fa1da76d69b9.svg
26488 open-sans-v15-latin-regular.61747992dd9a412e601e.ttf
17704 open-sans-v15-latin-regular.87a454b233bedae23f8d.woff
15050 open-sans-v15-latin-regular.a356e361ee6765f3d6d4.eot
14048 open-sans-v15-latin-regular.ec806460121999bcfb12.woff2
  118 password-meter.eea288d50533d7995ec1.png
85747 polyfills-es2015.16ceb16b32a8545e7574.js
180943 polyfills-es5.006e024480b437b2c337.js
39572 primeicons.04701ca33ce96d325419.ttf
39648 primeicons.1d79a0559e5f13294dee.woff
39748 primeicons.c2e128a00fca2640240d.eot
163568 primeicons.fa2c83d2f35244bb10dc.svg
 2289 runtime-es2015.2e6109e62d8c1599b4b0.js
 2286 runtime-es5.2e6109e62d8c1599b4b0.js
557012 scripts.d206f7319f6122bd4e1d.js
407942 styles.47345563e6287b4278d5.css

Con esto ya lo tendrías todo! espero que te sea de utilidad !

Google Analytics en Angular

Si trabajas con Angular (o algún sistema similar SPA) y has intentado usar google analytics para monitorizar tu página, te habrás dado cuenta de que GA no es capaz de «seguir» los cambios de URL de las SPA.

para que el seguimiento se realize correctamente, debemos llamar la función de Google Analytics que notifica que una URL ha sido visitada. Existen diferentes maneras de hacerlo, pero te explicaré la que yo uso 😉


El Script de Google Analytics

Voy a dar por supuesto que te has registrado en Google Analytics y tienes ya tu identificador ( del estilo ‘UA-4353454353-6’) Si no es así, ya sabes lo que toca!

Añade este código en tu ‘index.html’

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXXXXXXX-X', 'auto');
</script>

Este es el código de Google que realizará el seguimiento. (Recuerda cambiar ‘UA-XXXXXXXXX-X’ por tu ID!!)

 

Fíjate que hemos eliminado del script original de GA la línea

ga('send', 'pageview');

Esta es la función que «comunica» la url que estamos visitando. La eliminamos por que la llamaremos desde el constructor de nuestros componentes.

Cargando las funciones GA en los componentes

Ahora, para poder llamar las funciones de Google Analytics desde nuestros componentes, vamos a instalar los @types/google.analytics que contienen las definiciones para Google Analytics. (Puedes consultar el paquete npm para más información)

Para instalarlas ejecutaremos en la carpeta de nuestro proyecto la siguiente instrucción:

npm install --save-dev @types/google.analytics

De esta manera tendremos disponibles las funciones ga() en nuestros componentes.

 

Llamando a desde el constructor del componente

Genial, pues solo nos falta llamar las funciones desde el componente que deseemos (hay que tener en cuenta que deberemos añadir este código a los constructores de cada componente que sea subsceptible de ser llamado por una ruta)
  constructor(
    private router: Router,
  ) {
    this.router.events.subscribe(event => {
    if (event instanceof NavigationEnd) {
        ga('set', 'page', event.urlAfterRedirects);
        ga('send', 'pageview');
    }
    });
  }
Como habrás adivinado, debes importar el Router de angular y el NavigationEnd para poder usarlos:
import { Router } from '@angular/router';
import { NavigationEnd } from '@angular/router';

En ocasiones algunos usuarios han reportado errores del estilo:
Cannot find name 'ga'. webpack: Failed to compile.
Esto es debido a que al compilar, angular no reconoce ga() como una función (aunque en realidad si esta disponible). Podemos solucionarlo fácilmente escribiendo la siguiente línea justo debajo de los imports:
declare var ga: Function;

Y con esto ya lo tienes! Tu página está monitorizada y podrás saber que le gusta a tus visitantes!

Usar un componente en varios módulos en Angular

Como ya debes haber averiguado si estas aquí, los componentes en tu aplicación de Angular sólo puede declararse dentro de un único módulo. Esto supone un problema debido a que en ocasiones queremos reutilizar un componente en diferentes partes de la aplicación (por ejemplo un datepicker o un Pipe).

Para resolver el problema lo más cómodo es declarar un módulo «shared» dentro del cual crearemos los componentes que queramos compartir en el resto de la aplicación.

Un ejemplo seria:

@NgModule({
  declarations: [ SharedComponent ],
  exports: [ SharedComponent ]
})
export class SharedModule {}

Después sólo necesitaremos importar el módulo allí donde queramos usarlo.

@NgModule({
   imports: [ SharedModule ]
})
export class ModuloQueUsaTuSharedComponent {}

Si tienes un grupo de Pipes que quieres usar en toda tu aplicación puede ser muy útil definir un módulo que los contenga a todos. Pero por supuesto, si piensas crear un componente para usarlo en diferentes aplicaciones debes definir un módulo que contenga tu componente de manera que puedas usarlo únicamente importándolo.

Ahora ya sabes cómo definir tus componentes dentro de un módulo como todo un profesional!

.woff2 Failed to load resource 404 NOT FOUND

Me he encontrado un error «común» en diferentes aplicaciones con Angular al colgarlas en servidores IIS. No es un fallo muy importante, pero puede sacar de quicio solucionarlo, ya que cuando debugas en local, no aparece este error.

Para solucionar este error, simplemente debes definir la extensión .woff2 con el mimeType «application/font-woff2» en el IIS.

Para hacer esto simplemente acudimos a la configuración mime del sitio web.

Alternativamente (y mi preferida) puedes establecer esta configuración en el webconfig, de esta manera no necesitarás tocar configuraciones del servidor (perfecto si no tienes acceso a la administración de IIS) y podrás mover la web de un lugar a otro con la seguridad de que funciona.

<system.webServer>
  <staticContent>
    <remove fileExtension=".woff2" />
    <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
  </staticContent>
</system.webServer>

Espero que te sea útil !

Lazy Loading en Angular

¿Para qué necesito Lazy Loading?

La mayoría de veces que creamos un proyecto en Angular (o al menos en mi caso) nos interesa que la aplicación cargue de una sola vez, de manera que sacrificando un pequeño período de tiempo al inicio, conseguimos que la velocidad y la fluidez sea mucho mas alta durante el resto de la navegación.

Para aplicaciones web usadas dentro de una red local, esta opción es más que perfecta, pero ¿qué podemos hacer si nuestra aplicación crece mucho y sobrecarga el navegador? ¿y si un tipo de usuario concreto sólo debe acceder a una parte muy pequeña de la aplicación?

Este problema lo solucionamos usando Lazy Loading, que básicamente significa que la aplicación web no se cargará de una sola vez al arranque, si no que se irá cargando a demanda según la necesidad del usuario.

Empezando: las rutas

Para empezar a trabajar con Lazy Loading lo primero es entender como carga las rutas Angular.

Si ya tenemos un proyecto, vamos a abrir nuestro fichero de rutas y si no es así puedes crear un nuevo proyecto especificando  el parámetro –routing para que te genere automáticamente un fichero de rutas:

ng new --routing

En mi caso, mi fichero es el siguiente:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { InicioComponent } from './inicio/inicio/inicio.component';
import { GalleryShowComponent } from './gallery-show/gallery-show/gallery-show.component';
import { AboutComponent } from './about/about/about.component';
import { GallerySelectorComponent } from './gallery-selector/gallery-selector/gallery-selector.component';

const routes: Routes = [
{ path: 'informatica', loadChildren: './inicio/inicio.module#InicioModule' },
{ path: 'gallery/view', loadChildren: './gallery-show/gallery-show.module#GalleryShowModule' },
{ path: 'about', loadChildren: './about/about.module#AboutModule' },
{ path: 'gallery', loadChildren: './gallery-selector/gallery-selector.module#GallerySelectorModule' },
{ path: '**', redirectTo: '/informatica'}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Aquí lo que estamos viendo es que nuestro módulo de rutas define unas rutas concretas para las diferentes páginas que tendremos, pero en lugar de referenciar una ruta a un componente, lo estamos referenciando a un módulo. Vamos a fijarnos en la galaeria (por ejemplo):

{ path: 'gallery', loadChildren: './gallery-selector/gallery-selector.module#GallerySelectorModule' },

Tenemos por un lado el path, donde especificamos la url y en loadChildren escribiremos la ruta al archivo del módulo que contiene nuestros componentes para esa página. La sintaxis especifica después de la ruta el nombre del módulo (separado por una ‘#’.

LoadChildren lo que nos indica es que el módulo tendrá sus propias rutas definidas, por lo que no las cargamos, simplemente cuando las necesitemos ya las buscará.

Fíjate que cargamos ya los componentes que forman parte de los módulos que vamos a usar.

Hasta aquí fácil no?

Creando nuestros módulos

Ahora tendremos que definir nuestros módulos y escribir en ellos las rutas que cargaremos. Para seguir con el ejemplo, os pondré el contenido del módulo gallery-selector (es el que esta funcionando en esta misma web 😀 )

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { GallerySelectorComponent } from './gallery-selector/gallery-selector.component';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', component: GallerySelectorComponent},
];


@NgModule({
  imports: [
    CommonModule, RouterModule.forChild(routes)
  ],
  declarations: [GallerySelectorComponent]
})
export class GallerySelectorModule { }

En este fichero debemos fijarnos sobre todo en cuatro cosas:

  • Importamos los componentes que vamos a usar y forman parte del módulo
  • Definimos una variable routes con las rutas definitivas
  • Importamos Routes y RouterModule
  • Añadimos en imports «RouterModule.forChild(routes)» para que las rutas estén disponibles cuando el archivo de rutas maestro lo consulte.

 

Comandos útiles:

//Generar un módulo nuevo
ng generate module mi-modulo

//Cenerar un componente dentro de un módulo
ng generate component mi-modulo/mi-componente

Probando la configuración

Llegados a este punto nuestra aplicación ya empezará a generar el Lazy loading, pero y si queremos comprobar que realmente funciona?

Sólo tenemos que usar las herramientas de desarrollador de Chrome y fijarnos en las Request que se generan:

Como puedes ver se genera una serie de peticione sque cargan los JS inciales y sólo al visitar un álbum se genera la petición de gallery-show.module.chunk.js

Esto mismo puedes comprobarlo en el apartado de galeria de la web y verlo por ti mismo! (Recuerda que una vez compilado angular renombra los módulos con un hash!)

Como curiosidad, comentar que como convenio se suele poner un + en cada carpeta que contiene un módulo cargado por Lazy Loading. De esta manera tendremos contentos hasta a los más puritanos!