11 min to read
Angular Universal
Holissss como estan??? Hoy quiero dedicarle este post a nuestro amigo Angular Universalllll. Si ya trabajaste con Angular Universal y pensas migrar, o si queres saber que es para decidir si aplicarlo o no en tu app oooooooo si te está haciendo renegar porque te dicen que es fácil pero no es taaaan asi puesss este post es para usted!
Qué es
Angular Universal es la tecnologia que nos permite ejecutar nuestras apps en Angular del lado del servidor, SSR (Server Side Rendering). SI, leiste bien. Estamos acostumbrados toda la vida (toda la vida? que le pasa?) a que las aplicaciones las construimos y servimos del lado del cliente (Javascript, con o sin tu framework favorito). PERO, Angular Universal nos promete construir nuestra aplicación y servirla directamente desde el servidor.
Cómo? Bueno básicamente nos ayuda a implementar ciertas características para que apliquemos en nuestras app y podamos prerenderizar , es decir, generamos HTML estaticos con todos los datos listos (por ejemplo con las respuestas de los servicios ya mockeados y demas hierbas) y eso lo servimos de una!
Qué logramos con esto? Nuestra aplicación tendrá altisimaaaaa velocidad en la carga inicial de esas paginas y rutas donde apliquemos la prerenderizacion y los HTML estaticos. Como estamos sirviendo ya el HTML, no estamos esperando ni las ejecuciones de llamadas a servicios, ni sus respuestas, nada de promesas ni observables, nada de nada, HTML y chau)
Qué hacemos con los datos dinámicos? No tenés que preocuparte por eso, ya que una vez que cargaste la app, ves el prerenderizado de Angular Universal pero luego ante un evento, ANGULAR toma control total y tu app sigue funcionando como siempre (con las llamadas a servicios, contenidos dinamicos, data binding, carga y renderizado dinamico, todo normal como siempre). Vos podes elegir si sólo prerenderizar el index.html de la carga inicial de tu app, y tambien elegir qué rutas queres que tengan ese prerenderizado y cuales no.
Configuración Fresh Starter
Si queres armar una app de cero en Angular y ponerle Universal, o si tenes ya una app en Angular pero nunca tuvo Universal, la configuración debería ser sencilla y la detallamos aca. Si venis renegando por otras migraciones, anda derecho a la proxima seccion!
Bueno arrancamos con el comando inicial
ng add @nguniversal/express-engine
Eso nos va a generar unos cuantos archivos base de configuración de nuestro servidor para levantar nuestra app SSR. Una vez que finaliza, toma la configuración de nuestro angular cli para configurar el server en base a nuestra app. Para correrlo usamos
npm run dev:ssr
Si abrimos nuestro local http://localhost:4200 vamos a ver nuestra app de siempre, PERO vamos a ver si la magia está ahi, abrimos el codigo fuente (ctrl + u) y revisamos el HTML. Si podemos verlo es que funciona, de hecho las versiones mas nuevas de universal le agregan un comentario: This page was prerendered with Angular Universal Cual es la diferencia? Si miras el codigo fuente de una app Angular normal vas a ver solo los meta tags, scripts, links etc PERO vas a ver el
<sc-root></sc-root>
```typescript
donde la app de Angular descansa (?) asi que no se ve nada.
Si miramos el codigo fuente de una app con Angular Universal, vamos a poder visualizar que hay dentro del root, vamos a ver body, divs, span, p, etc todo nuestro contenido HTML vamos a poder verlo!
## Configuración Advanced
![enter image description here](https://static01.nyt.com/images/2016/08/05/us/05onfire1_xp/05onfire1_xp-articleLarge-v2.jpg?quality=75&auto=webp&disable=upscale)
En esta seccion quiero detallar un poco algunos trucos y consejos para poder lograr que funcione Universal con la llegada de **Ivy**, el nuevo motor de compilado y renderizacion de Angular 9. Si tenemos nuestra app en Angular con Universal instalado y configurado PERO vamos a migrar a otra version de Angular (9 o mas) las cosas empiezan a fallar y la migracion de Universal no es tan sencilla.
De hecho, los comandos de Universal que usabas antes van a fallar.
Para profundizar un poco, el problema de todo esto reside en la compatibilidad de la migración, a partir de Angular 9 se introduce el compilador **Ivy**, y esto afecta a Universal tambien, sobre todo por la forma que busca y compila los modulos, que no es la misma de antes.
Entonces, la **_manera_** en la que veniamos implementando Universal cambió.
Si estás en Angular 8 o anterior, la forma de aplicar Universal era mas compleja y mas o menos asi, tenias archivos de _server.ts_ y _prerender.ts_ que podias tunear un poco a mano, y además tenias tu archivo de webpack con su config y su comando para lograr que el servidor levante y prerenderice todo como corresponde.
Ahora, a partir de Angular 9 en adelante, Universal necesita adaptarse a **Ivy**, asi que probablemente tengamos inconvenientes por compatibilidad. Entonces...
Ya no existe un prerender.ts, ni tampoco la configuracion custom de webpack. Todo esto es delegado y quien se encarga ahora es el paquete que instalamos de Universal PERO necesitamos adaptar y confirmar algunas configuraciones para que realmente los cambios tengan efecto y podamos continuar con Universal en nuestra app migrada.
Aca van los puntos mas importantes, chequeate todo esto que lo sacas andandoooo
![enter image description here](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTc5bACPeIWofEypyqv3XASoCcZQsbxQVbg1w&usqp=CAU)
**1-** En **app.server.module.ts** antes tenias esto
```typescript
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule
Ahora queda asi:
@NgModule({
imports: [
AppModule,
ServerModule
Y tambien borra el import
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
Si, volamos el ModuleMapLoaderModule, porque no es mas compatible con Ivy.
2- El server.ts que te genera nuevo (con el comando de nguniversal) tiene la mayoria de cambios hechos, pero vas a necesitar sumar esto si tenes errores por tema window y/o document
const domino = require('domino');
const template = join(process.cwd(), 'dist/browser', 'index.html').toString();
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;
3- En main.server.ts tenes que borrar lo siguiente
export { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
como dijimos antes, el moduleMapper ya no es compatible con Ivy.
De paso chequea y confirma que en este archivo tengas esta linea que funciona con el nuevo render de modulos:
export { renderModule, renderModuleFactory } from '@angular/platform-server';
4- En tsconfig.server.json te tiene que quedar:
"files": [
"src/main.server.ts",
"server.ts"
5- En Angular cli, verificar la config del serve local “serve-ssr”
"serve-ssr": {
"builder": "@nguniversal/builders:ssr-dev-server",
"options": {
"browserTarget": ":build", //este va a tomar las configs de nuestro objeto build
"serverTarget": ":server" //este tomara la config de nuestro objeto server que veremos mas abajo
},
"configurations": {
"production": {
"browserTarget": ":build:production",
"serverTarget": ":server:production"
}
}
Nota: Por cada ambiente necesitas adaptar el objeto configurations de acuerdo a tus environments y asi tener 2, 3 o cuantos objetos de entorno tengas, por ejemplo:
"production": {
"browserTarget": ":build:",
"serverTarget": ":server:"
}
6- En Angular cli, verificar config del “server”
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "/server.ts",
"tsConfig": "/tsconfig.server.json"
},
"configurations": {
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
"sourceMap": true,
"optimization": true
...
},
7- En Angular cli, verificar config del “prerender”
"prerender": {
"builder": "@nguniversal/builders:prerender",
"options": {
"browserTarget": ":build:production",
"serverTarget": ":server:production",
"routes": [
"/"
]
},
"configurations": {
"dev": {
"browserTarget": ":build:",
"serverTarget": ":server:",
"routes": [
"/"
]
},
8- Si te surgen algunos problemas o errores de tipado cuando corres el server.ts, podes probar con estas versiones de universal y express que actualmente funcionan sin errores:
"@nguniversal/builders": "^10.0.0-rc.1",
"@types/express": "^4.17.0"
Deploys
Para deployar SSR vas a necesitar que se corran los comandos del build SSR, server SSR y tambien del prerender.
Si tenés más de un ambiente configurado, necesitas agregar en las config de esas operaciones (build, server y prerender) arriba mencionadas por cada entorno, y en los comandos hacer uso de esas variables para deployar la build correspondiente.
Si podes mover los archivos que quedan en dist/browser a la carpeta donde se deploya tu app, eso es todo. En caso de que no puedas, deberás correr node dist/server/main.js en un server node para poder levantarlo.
Por último, en caso de que necesites que tu build (sea o no SSR) se compile y deploye en una carpeta x, recorda que podes configurarlo en Angular cli asi:
"architect": {
"build": {
"options": {
"baseHref": "/app_folder/",
"deployUrl": "/app_folder/",
Conclusión
Si tenemos una app en Angular y queremos que cargue rapido el primer page load y/o la carga de ciertas rutas especificas, que demos prioridad a SEO y que Google indexe correctamente nuestra app, Angular Universal es la opción! Angular Universal es facil mmmmmm
Si nunca lo implementaste posiblemente si, porque practicamente automatizó todo lo que antes era manual. Pero, si ya tenias uno y te comiste el garron de Ivy, tenes que migrar y configurar a mano todo lo que la migracion no pudo hacer automáticamente.
Tambien puede ocurrir que tengas mas de una aplicacion (tipo project) corriendo en el mismo repositorio, si se configuran las variables arriba mencionadas, con los nombres de esos proyectos en app_name y los entornos que necesites, todas las solucionas aplican tambien a este escenario. Durante esta migracion de compatibilidad entre Universal e Ivy, no encontre tanta documentacion asi que espero que este post te sirva de guia.