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
Sí, hay un módulo dedicado,
TransferHttpCacheModule
que hace exactamente lo que queremos. Registrará un interceptor y cuando el SSR recupere los datos, guardará los datos en el estado y el frontend obtendrá esos datos, del estado, sin hacer una solicitud adicional.
Todo lo que necesita hacer es agregar
TransferHttpCacheModule
desde @ nguniversal / common dentro de la matriz de importaciones de su módulo de aplicación.
Luego, importe
ServerTransferStateModule
desde @ 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
Como ejemplo (que fue un problema real para mí) considere la siguiente situación. Cuando cualquier usuario desea abrir una página pública con datos públicos, SSR hará una solicitud al punto final de la API, obtendrá los datos, los representará y los enviará de vuelta al usuario. Ahora imagine que tiene una base de usuarios muy grande y se solicita la misma página de 10 a 20 veces en un segundo. Cada segundo SSR hará una solicitud, obtendrá la mayoría de los mismos datos y se los dará a transferState. Esto ya es malo porque estamos haciendo demasiadas solicitudes incluso aunque sabemos que los datos probablemente sean los mismos.
Ahora piense que SSR intentará obtener algunos datos públicos del servidor de terceros. Digamos una lista de presidentes estadounidenses. Y ese servidor de terceros tiene una limitación de 10 solicitudes de una dirección IP en un segundo. Ahora, si más de 10 personas al mismo tiempo intentan abrir esa página, SSR hará más de 10 solicitudes a una API de terceros y lo más probable es que sean prohibidas. El problema es que cuando todas las solicitudes son manejadas por el servidor, el servidor en sí es solo una computadora con su propia IP, por lo que todas las solicitudes serán de la misma dirección IP.
Ahora piense que SSR intentará obtener algunos datos públicos del servidor de terceros. Digamos una lista de presidentes estadounidenses. Y ese servidor de terceros tiene una limitación de 10 solicitudes de una dirección IP en un segundo. Ahora, si más de 10 personas al mismo tiempo intentan abrir esa página, SSR hará más de 10 solicitudes a una API de terceros y lo más probable es que sean prohibidas. El problema es que cuando todas las solicitudes son manejadas por el servidor, el servidor en sí es solo una computadora con su propia IP, por lo que todas las solicitudes serán de la misma dirección IP.
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
.- 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.ts
en el módulo del servidorproveedores: [{
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
BrowserStateInterceptor
el módulo de su aplicaciónproveedores: [
{
proporcionar: HTTP_INTERCEPTORS,
useClass: BrowserStateInterceptor,
multi: true ,
}
],
5. Y finalmente eliminar
TransferHttpCacheModule
del módulo de la aplicación
Así que ahora tenemos casi las mismas características
TransferHttpCacheModule
pero 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-cache
módulo de nodo.npm i memory-cache
También es posible que desee instalar
@types/memory-cache
para 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-cache
El 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-cache
su 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-cache
mó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.
0 Comentarios