Angular Universal es una excelente solución para la representación angular del lado del servidor. Nos permite establecer metaetiquetas para SEO, para compartir en redes sociales, pero lo más importante es que muestra nuestra página en el servidor para que el usuario ya obtenga la página, lo que significa un rendimiento más rápido y uniforme.
Sin embargo, la mayoría de las veces los desarrolladores no utilizan todas las ventajas de SSR. Por ejemplo, en algunos casos bloquean las llamadas api (isPlatFormBrowser), por lo tanto, el servidor procesa una página sin datos y el cliente vuelve a procesar con datos en la interfaz (los datos se obtienen en la interfaz) lo que conduce a la llamada falla "parpadeante". En otros casos, el servidor (ssr), al representar la página, realiza la solicitud a la API, pero la misma solicitud también se realiza en la interfaz, lo que en este momento significa una doble solicitud de los datos que ya se han obtenido del servidor .
En cambio, todo esto debería permitir que Universal busque todas las solicitudes GET en el servidor, almacénelas en algún lugar y cuando el cliente (frontend) haga la solicitud, obtendrá datos ya recuperados y no hará ninguna solicitud adicional a la API servidor.

Solución predeterminada TransferHttpCacheModule

Todo lo que necesita hacer es agregar TransferHttpCacheModuledesde @ nguniversal / common dentro de la matriz de importaciones de su módulo de aplicación.
Luego, importe ServerTransferStateModuledesde @ angular / platform-server en su módulo de Servidor.
Después de esto, si no hay partes de bloqueo, como verificaciones para el servidor / navegador, verá que la página se procesa pero no hay llamadas en la pestaña de red, porque Angular Universal ya obtuvo los datos y pasó al frente como un estado. Sin embargo, hay una cosa a considerar. Esto es todo lo que hace, nada más. En muchos casos, queremos escribir nuestro propio mecanismo de almacenamiento en caché, que desafortunadamente no es posible con este módulo. Sin embargo, la mejor parte es que podemos escribir nuestro propio interceptor y cambiar / obtener el estado.

El problema

Para resolver este problema en particular y el problema de solicitar los mismos datos sin cambios una y otra vez, necesitamos escribir nuestra propia lógica para el estado de transferencia.
Almacenamiento en caché y transferencia manual
Primero escribamos el mismo (o muy similar) interceptor que se usa en TransferHttpCacheModule.
  1. Crea un nuevo archivo serverstate.interceptor.ts
Cuando el SSR obtiene los datos de la API, el interceptor utilizará la URL de solicitud como clave y almacenará el cuerpo de respuesta real en un objeto especial (transferState).
2. Registrarse serverstate.interceptor.tsen el módulo del servidor
proveedores: [{ 
  proporcionar: HTTP_INTERCEPTORS, 
  useClass: ServerStateInterceptor, 
  multi: true }],
3. Ahora todas las respuestas de las solicitudes de SSR se almacenarán en transferState. Necesitamos crear otro interceptor, esta vez para frontend, de modo que en lugar de volver a buscar los datos, los tome del estado si existe allí.
Crear nuevo archivo browserstate.interceptor.ts
Primero, estamos verificando si el método de solicitud es GET, si no, pasamos la solicitud al siguiente interceptor o al cliente http.
Entonces estamos tratando de obtener datos guardados de transferState. Si hay datos, creamos un nuevo objeto HttpResponse con nuestros datos y los devolvemos, por lo que ningún otro interceptor interceptará esta solicitud. Si no hay nada en transferState para la clave dada (url), estamos pasando la solicitud al siguiente interceptor o cliente http para que realmente haga la solicitud.
4. Agregue BrowserStateInterceptorel módulo de su aplicación
proveedores: [ 
  { 
    proporcionar: HTTP_INTERCEPTORS, 
    useClass: BrowserStateInterceptor, 
    multi: true , 
  } 
],
5. Y finalmente eliminar TransferHttpCacheModuledel módulo de la aplicación
Así que ahora tenemos casi las mismas características TransferHttpCacheModulepero con nuestra solución personalizada. Sin embargo, esto no resuelve nuestro problema con el almacenamiento en caché y las solicitudes frecuentes.
Almacenamiento en caché
La idea es simple. Cuando un usuario abre una página por primera vez, SSR hará una solicitud al servidor API, obtendrá esos datos, guardará en transferState Y también lo guardará con la misma clave (url de solicitud) en alguna base de datos local. Para las siguientes solicitudes de los usuarios, comprobaremos si para la clave dada (url de solicitud) hay una entrada en nuestra base de datos local. En caso afirmativo, lo guardaremos en transferState y lo devolveremos como HttpResponse, para que no se realice una solicitud real al servidor API. Si no, repetiremos el primer paso, buscar desde la API, guardarlo en transferState y en la base de datos local.
Puede implementar su propia lógica para la base de datos del servidor local (incluso puede ser una matriz u objeto simple). Voy a usar el memory-cachemódulo de nodo.
npm i memory-cache
También es posible que desee instalar @types/memory-cachepara una mejor verificación de tipos.
Inclúyalo en su serverstate.interceptor.ts file
importar * como memoryCache desde 'memory-cache';
Primero, modifiquemos la lógica dentro de next.handle () para agregar respuestas del servidor API en nuestra base de datos local
Cambie la siguiente parte (se resalta el nuevo código agregado)
if (event instanceof HttpResponse) { 
  this .transferState.set (makeStateKey (req.url), event.body); 
  memoryCache.put (req.url, event.body); 
}
Y antes de regresar next.handle () necesitamos verificar nuestra base de datos local y el valor de retorno desde allí
const cachedData = memoryCache.get (req.url); 
if (cachedData) { 
  this .transferState.set (makeStateKey (req.url), cachedData); 
    retorno de ( new HttpResponse ({body: cachedData, status: 200})); 
} return next.handle (req) .pipe (
.....
Con esto estamos casi listos. Ahora solo se realizará una solicitud de SSR al servidor API, y todos los usuarios obtendrán datos guardados de nuestra base de datos local del servidor SSR. Sin embargo, esto nos trae otro problema. Probablemente no queremos guardar estos datos en nuestra base de datos local para siempre. Al menos podríamos querer actualizarlo a veces. Bueno, podemos crear una función que borre todos los DB locales o las teclas seleccionadas desde allí. Pero tenemos que activar esa función de alguna manera (tal vez con una llamada API). O podemos hacerlo aún mejor. Podemos establecer la hora e invalidar nuestros datos locales después de esa hora. memory-cacheEl módulo tiene soporte de claves con tiempo de caducidad. Si usa una solución personalizada, puede implementar esto con setTimeout().
Ahora modifiquemos el código nuevamente para que cada dato de respuesta almacenado en nuestra base de datos local sea válido durante 5 minutos. Durante esos 5 minutos, todos los usuarios obtendrán datos de nuestra base de datos local, por lo tanto, no se realizarán llamadas API. Después de invalidar los datos locales, la nueva solicitud activará una nueva llamada a la API, y los nuevos datos se almacenarán en nuestra base de datos local durante otros 5 minutos y así sucesivamente.
if (event instanceof HttpResponse) { 
  this .transferState.set (makeStateKey (req.url), event.body); 
  memoryCache.put (req.url, event.body, 6000 * 5 ); 
}
¡Ahora si ejecuta su aplicación, verá que nada funciona! Habrá una carga infinita (en realidad, en este caso, llevará 5 minutos).
En realidad, el problema no está relacionado con memory-cachesu solución personalizada. El problema es con Angular Universal que maneja el código asíncrono.
Angular Universal no completará la representación de la página hasta que haya eventos asincrónicos inacabados
Nuestro memory-cachemódulo utiliza setTimeout()internamente y en nuestro caso Universal esperará 5 minutos para que la cola de tareas esté vacía y solo después de eso terminará su trabajo.
Como paso final, para resolver este problema, podemos decirle a Angular que ejecute este código fuera de la zona angular.
constructor ( private transferState: TransferState, private ngZone: NgZone ) {} 
.... return next.handle (req) .pipe ( 
  tap (event => { 
    if (event instanceof HttpResponse) { 
      this .transferState.set (makeStateKey (req) .url), event.body); 
      this .ngZone.runOutsideAngular (() => { 
        memoryCache.put (req.url, event.body, 1000 * 60); 
      })

     } 
  }) 
);
Así que ahora, en lugar del predeterminado y básico TransferHttpCacheModule, tenemos un mecanismo transferState personalizado totalmente funcional que se puede modificar de muchas otras maneras.

Gracias por leer.