Esta guía describe Angular Universal, una tecnología que procesa aplicaciones angulares en el servidor.

Una aplicación Angular normal se ejecuta en el navegador, renderizando páginas en el DOM en respuesta a las acciones del usuario. Angular Universal se ejecuta en el servidor, generando static páginas de la aplicación que luego se inician en el cliente. Esto significa que la aplicación generalmente se procesa más rápidamente, lo que brinda a los usuarios la oportunidad de ver el diseño de la aplicación antes de que se vuelva completamente interactiva.

Para obtener una visión más detallada de las diferentes técnicas y conceptos relacionados con SSR, consulte este artículo.

Puede preparar fácilmente una aplicación para la renderización del lado del servidor utilizando Angular CLI. El esquema de CLI @nguniversal/express-engine realiza los pasos necesarios, como se describe a continuación.

Nota:Descarga el código de muestra terminado, que se ejecuta en un Node.js® Express servidor.

Tutorial universal

El tutorial Tour of Heroes es la base de este tutorial.

En este ejemplo, Angular CLI compila y agrupa la versión universal de la aplicación con el compilador Ahead-of-Time (AOT). Un servidor web Node.js Express compila páginas HTML con Universal en función de las solicitudes de los clientes.

Para crear el módulo de la aplicación del lado del servidor, app.server.module.ts, ejecute el siguiente comando CLI.

ng add @nguniversal/express-engine

El comando crea la siguiente estructura de carpetas.

src/
  index.html                 app web page
  main.ts                    bootstrapper for client app
  main.server.ts             * bootstrapper for server app
  style.css                  styles for the app
  app/ ...                   application code
    app.server.module.ts     * server-side application module
server.ts                    * express web server
tsconfig.json                TypeScript base configuration
tsconfig.app.json            TypeScript browser application configuration
tsconfig.server.json         TypeScript server application configuration
tsconfig.spec.json           TypeScript tests configuration

Los archivos marcados con * son nuevos y no están en la muestra del tutorial original.

Universal en acción

Para comenzar a renderizar su aplicación con Universal en su sistema local, use el siguiente comando.

npm run dev:ssr

Abra un navegador y navegue hasta http: // localhost: 4200 /. Debería ver la página del panel familiar Tour of Heroes.

Navegación a través de routerLinks funciona correctamente porque usan el ancla nativa () etiquetas. Puedes ir desde el Panel de control a la página de Héroes y viceversa. Puedes hacer clic en un héroe en la página del Tablero para mostrar su página de Detalles.

Si reduce la velocidad de su red para que los scripts del lado del cliente tarden más en descargarse (instrucciones a continuación), notará:

  • Hacer clic en un héroe en la página de Héroes no hace nada.
  • No puedes agregar ni eliminar un héroe.
  • Se ignora el cuadro de búsqueda en la página del Panel de control.
  • los atrás y Ahorrar los botones de la página Detalles no funcionan.

Eventos de usuario distintos de routerLink los clics no son compatibles. Debe esperar a que la aplicación cliente completa se inicie y se ejecute, o almacene los eventos usando bibliotecas como prearranque, que le permiten reproducir estos eventos una vez que se cargan los scripts del lado del cliente.

La transición de la aplicación renderizada por el servidor a la aplicación cliente ocurre rápidamente en una máquina de desarrollo, pero siempre debe probar sus aplicaciones en escenarios del mundo real.

Puede simular una red más lenta para ver la transición con mayor claridad de la siguiente manera:

  1. Abra las herramientas de desarrollo de Chrome y vaya a la pestaña Red.
  2. Encuentra el Limitación de la red menú desplegable en el extremo derecho de la barra de menú.
  3. Pruebe una de las velocidades “3G”.

La aplicación renderizada por el servidor aún se inicia rápidamente, pero la aplicación cliente completa puede tardar unos segundos en cargarse.

¿Por qué utilizar la representación del lado del servidor?

Hay tres razones principales para crear una versión universal de su aplicación.

  1. Facilite los rastreadores web a través de optimización de motores de búsqueda (SEO)
  2. Mejore el rendimiento en dispositivos móviles y de baja potencia
  3. Muestre la primera página rápidamente con un pintura de primer contenido (FCP)

Facilitar los rastreadores web (SEO)

Google, Bing, Facebook, Twitter y otros sitios de redes sociales dependen de los rastreadores web para indexar el contenido de su aplicación y hacer que ese contenido se pueda buscar en la web. Es posible que estos rastreadores web no puedan navegar e indexar su aplicación Angular altamente interactiva como lo haría un usuario humano.

Angular Universal puede generar un static versión de su aplicación que se puede buscar, enlazar y navegar fácilmente sin JavaScript. Universal también ofrece una vista previa del sitio, ya que cada URL devuelve una página completamente renderizada.

Mejore el rendimiento en dispositivos móviles y de baja potencia

Algunos dispositivos no son compatibles con JavaScript o ejecutan JavaScript tan mal que la experiencia del usuario es inaceptable. Para estos casos, es posible que necesite una versión de la aplicación sin JavaScript, renderizada por el servidor. Esta versión, aunque limitada, puede ser la única alternativa práctica para las personas que de otra manera no podrían usar la aplicación en absoluto.

Muestre la primera página rápidamente

Mostrar la primera página rápidamente puede ser fundamental para la participación del usuario. Las páginas que se cargan más rápido funcionan mejor, incluso con cambios tan pequeños como 100 ms. Es posible que su aplicación deba iniciarse más rápido para atraer a estos usuarios antes de que decidan hacer otra cosa.

Con Angular Universal, puede generar páginas de destino para la aplicación que se parecen a la aplicación completa. Las páginas son HTML puro y pueden mostrarse incluso si JavaScript está desactivado. Las páginas no manejan eventos del navegador, pero hacer apoyar la navegación a través del sitio usando routerLink.

En la práctica, servirás un static versión de la página de destino para mantener la atención del usuario. Al mismo tiempo, cargará la aplicación Angular completa detrás de ella. El usuario percibe un rendimiento casi instantáneo desde la página de destino y obtiene la experiencia interactiva completa después de que se carga la aplicación completa.

Servidores web universales

Un servidor web universal responde a las solicitudes de la página de la aplicación con static HTML renderizado por el motor de plantilla universal. El servidor recibe y responde a las solicitudes HTTP de los clientes (generalmente navegadores) y sirve static activos como scripts, CSS e imágenes. Puede responder a solicitudes de datos, ya sea directamente o como un proxy a un servidor de datos separado.

El servidor web de muestra para esta guía se basa en el popular Rápido estructura.

Nota:Alguna La tecnología de servidor web puede servir a una aplicación Universal siempre que pueda llamar a Universal renderModule() función. Los principios y puntos de decisión discutidos aquí se aplican a cualquier tecnología de servidor web.

Las aplicaciones universales usan Angular platform-server paquete (a diferencia de platform-browser), que proporciona implementaciones de servidor del DOM, XMLHttpRequesty otras funciones de bajo nivel que no dependen de un navegador.

El servidor (Node.js Express en el ejemplo de esta guía) pasa las solicitudes de los clientes para las páginas de la aplicación al NgUniversal ngExpressEngine. Debajo del capó, esto llama Universal’s renderModule() función, al tiempo que proporciona almacenamiento en caché y otras utilidades útiles.

los renderModule() la función toma como entradas un plantilla Página HTML (normalmente index.html), un Angular módulo que contiene componentes, y un ruta que determina qué componentes mostrar. La ruta proviene de la solicitud del cliente al servidor.

Cada solicitud da como resultado la vista adecuada para la ruta solicitada. los renderModule() La función renderiza la vista dentro de la etiqueta de la plantilla, creando una página HTML terminada para el cliente.

Finalmente, el servidor devuelve la página renderizada al cliente.

Trabajando alrededor de las API del navegador

Debido a que una aplicación universal no se ejecuta en el navegador, es posible que falten algunas de las API y capacidades del navegador en el servidor.

Por ejemplo, las aplicaciones del lado del servidor no pueden hacer referencia a objetos globales solo del navegador, como window, document, navigator, o location.

Angular proporciona algunas abstracciones inyectables sobre estos objetos, como Location o DOCUMENT; puede sustituir adecuadamente estas API. Si Angular no lo proporciona, es posible escribir nuevas abstracciones que deleguen a las API del navegador mientras está en el navegador y a una implementación alternativa mientras está en el servidor (también conocido como shimming).

De manera similar, sin eventos de mouse o teclado, una aplicación del lado del servidor no puede depender de que un usuario haga clic en un botón para mostrar un componente. La aplicación debe determinar qué renderizar basándose únicamente en la solicitud entrante del cliente. Este es un buen argumento para hacer que la aplicación sea enrutable.

Motor de plantilla universal

Lo importante en el server.ts el archivo es el ngExpressEngine() función.

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html',ngExpressEngine(
  bootstrap: AppServerModule,));

los ngExpressEngine() función es un envoltorio alrededor de Universal renderModule() función que convierte las solicitudes de un cliente en páginas HTML renderizadas por el servidor. Acepta un objeto con las siguientes propiedades:

  • bootstrap: La raíz NgModule o NgModule factory que se utilizará para iniciar la aplicación al renderizar en el servidor. Para la aplicación de ejemplo, es AppServerModule. Es el puente entre el renderizador universal del lado del servidor y la aplicación Angular.
  • extraProviders: Esto es opcional y le permite especificar proveedores de dependencia que se aplican solo cuando se procesa la aplicación en el servidor. Puede hacer esto cuando su aplicación necesita información que solo puede determinar la instancia del servidor que se está ejecutando actualmente.

los ngExpressEngine() la función devuelve un Promise devolución de llamada que se resuelve en la página renderizada. Depende del motor decidir qué hacer con esa página. Este motor Promise callback devuelve la página renderizada al servidor web, que luego la reenvía al cliente en la respuesta HTTP.

Nota: Estos envoltorios ayudan a ocultar la complejidad del renderModule() función. Hay más envoltorios para diferentes tecnologías de backend en el Repositorio universal.

Filtrado de URL de solicitud

NOTA: El comportamiento básico que se describe a continuación se maneja automáticamente cuando se usa el esquema NgUniversal Express. Esto es útil cuando se intenta comprender el comportamiento subyacente o replicarlo sin utilizar el esquema.

El servidor web debe distinguir solicitudes de la página de la aplicación de otro tipo de solicitudes.

No es tan simple como interceptar una solicitud a la dirección raíz /. El navegador podría solicitar una de las rutas de la aplicación, como /dashboard, /heroes, o /detail:12. De hecho, si la aplicación solo la procesara el servidor, cada el enlace de la aplicación en el que se haga clic llegará al servidor como una URL de navegación destinado al enrutador.

Afortunadamente, las rutas de las aplicaciones tienen algo en común: sus URL carecen de extensiones de archivo. (Las solicitudes de datos también carecen de extensiones, pero son fáciles de reconocer porque siempre comienzan con /api.) Todos static las solicitudes de activos tienen una extensión de archivo (como main.js o /node_modules/zone.js/bundles/zone.umd.js).

Debido a que usamos el enrutamiento, podemos reconocer fácilmente los tres tipos de solicitudes y manejarlas de manera diferente.

  1. Solicitud de datos: solicitud de URL que comienza /api.
  2. Navegación de la aplicación: URL de solicitud sin extensión de archivo.
  3. Activo estático: todas las demás solicitudes.

Un servidor Node.js Express es una canalización de middleware que filtra y procesa solicitudes una tras otra. Configura la canalización del servidor Node.js Express con llamadas a server.get() como este para solicitudes de datos.

// TODO: implement data requests securely
server.get('/api/**',(req, res)=>
  res.status(404).send('data requests are not yet supported'););

Nota: Este servidor de muestra no maneja solicitudes de datos.

El módulo “API web en memoria” del tutorial, una herramienta de demostración y desarrollo, intercepta todas las llamadas HTTP y simula el comportamiento de un servidor de datos remoto. En la práctica, eliminaría ese módulo y registraría su middleware de API web en el servidor aquí.

El siguiente código filtra las URL de solicitud sin extensiones y las trata como solicitudes de navegación.

// All regular routes use the Universal engine
server.get('*',(req, res)=>
  res.render(indexHtml, req, providers:[ provide:APP_BASE_HREF, useValue: req.baseUrl ]););

Servicio static archivos de forma segura

Un solo server.use() trata todas las demás URL como solicitudes de static activos como JavaScript, imágenes y archivos de estilo.

Para asegurarse de que los clientes solo puedan descargar los archivos que se les permite ver, coloque todos los archivos de activos de cara al cliente en el /dist carpeta y solo respeta las solicitudes de archivos del /dist carpeta.

El siguiente código de Node.js Express enruta todas las solicitudes restantes a /disty devuelve un 404 - NOT FOUND error si no se encuentra el archivo.

// Serve static files from /browser
server.get('*.*', express.static(distFolder,
  maxAge:'1y'));

Usar URL absolutas para solicitudes HTTP (datos) en el servidor

El tutorial HeroService y HeroSearchService delegar al Angular HttpClient módulo para obtener datos de la aplicación. Estos servicios envían solicitudes a relativo URL como api/heroes. En una aplicación renderizada del lado del servidor, las URL HTTP deben ser absoluto (por ejemplo, https://my-server.com/api/heroes). Esto significa que las URL deben convertirse de alguna manera a absolutas cuando se ejecutan en el servidor y dejarse relativas cuando se ejecutan en el navegador.

Si está utilizando uno de los @nguniversal/*-engine paquetes (como @nguniversal/express-engine), esto se soluciona automáticamente. No necesita hacer nada para que las URL relativas funcionen en el servidor.

Si, por alguna razón, no está utilizando un @nguniversal/*-engine paquete, es posible que deba manejarlo usted mismo.

La solución recomendada es pasar la URL de solicitud completa a la options argumento de renderModule () o renderModuleFactory () (dependiendo de lo que use para renderizar AppServerModule en el servidor). Esta opción es la menos intrusiva ya que no requiere ningún cambio en la aplicación. Aquí, “URL de solicitud” se refiere a la URL de la solicitud como respuesta a la que se está procesando la aplicación en el servidor. Por ejemplo, si el cliente solicitó https://my-server.com/dashboard y está procesando la aplicación en el servidor para responder a esa solicitud, options.url debe establecerse en https://my-server.com/dashboard.

Ahora, en cada solicitud HTTP realizada como parte de la representación de la aplicación en el servidor, Angular puede resolver correctamente la URL de la solicitud en una URL absoluta, utilizando el options.url.