Tempo di lettura: 7 minuti
ANGULAR UNIVERSAL
In Angular (vedi il primo articolo) l’intera applicazione è elaborata direttamente dal browser , a differenza di linguaggi quali Php,Asp.net, Python etc.. non necessita quindi di un server per elaborarne il linguaggio.Questo potrebbe essere penalizzante soprattutto quando si utilizzano dispositivi mobili i quali hanno limitate capacità di elaborazione rispetto ai classici pc da tavolo.Per ovviare a tale inconveniente ci viene incontro ANGULAR UNIVERSAL il quale consente ad un server di qualsiasi natura di elaborare l’intera applicazione generando una serie di pagine statiche mediante un processo chiamato SERVER SIDE RENDERING.
Ciò consente, inoltre , di gestire al meglio gli aspetti legati alla indicizzazione delle pagine (‘view’) nei motori di ricerca visto che Angular Universal commuta ogni url navigabile in pagina statica.
In pratica, la richiesta del visitatore che si collega per la prima volta al tuo sito Angular, verrà passata al server che caricherà velocemente la prima pagina, e nel frattempo, il browser scaricherà l’intera applicazione. Non appena finito l’upload sul client avviene la commutazione dalla versione presente sul server a quella presente in locale consentendoci di sfruttare tutte le potenzialità della nostra applicazione.
Per le piccole applicazioni forse è consigliabile non utilizzare Angula Universal in quanto per poterlo sfruttare occorrono una serie di passaggi non semplicissimi, inoltre nel server,ovviamente, non saranno disponibili tutti i classici oggetti nativi del browser, come window,document, navigator, location oltre a non saper intercettare il click sul bottone.
Ad ogni modo vediamo come installarlo.
INSTALLARE LE LIBRERIE MANCANTI
Aprite il vostro progetto ella root principale , quindi aprite il terminale e digitate:
npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine
MODIFICATE IL MODULO RADICE (app.modules.ts)
Per poter permettere la commutazione dell’applicazione presente sul server a quella presente sul client dobbiamo modificare il file app.modules.ts:
@NgModule({
...
imports : [
...
// BrowserModule,
BrowserModule.withServerTransition({ appId: 'app-pizza' }),
]
In pratica commentate l’importazione di BrowserModule ed aggiungete la seconda riga. La stringa app-pizza è una stringa generica che serve ad Angular per distinguere le pagine generate nel server.
MODIFICATE IL FILE angular.json
Per poter creare due spezzoni dell’applicazione (client e server) è necessario modificare la cartella in cui verrà generato il progetto in fase di produzione.
La cartella da usare sarà ‘dist/browser‘ anzi che ‘dist/appizza‘.
Quindi aprite il file angular.json e modificate la seguente riga:
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/browser",
...
}
}
MODIFICARE GLI URL
Se sviluppate un applicazione che interroga un END POINT interno allo stesso dominio in cui risiede la vostra app allora dovrete cambiare tutti gli indirizzi relativi a questo rendendoli assoluti. Noi realizzeremo un’applicazione che interroga un server remoto per cui lo saranno di già.
AGGIUNGERE UN MODULO RADICE A LIVELLO SERVER
Per far compilare l’app nel server è necessario creare un modulo che sia in grado di farlo quindi create il file app.server.module.ts (root/src/app/app.server.module.ts)e contemporaneamente il file ‘miccia’ che faccia partire l’applicazione partendo dal modulo suddetto: main.server.ts (root/src/main.server.ts).
All’interno di app.server.module.ts scrivete:
import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; import { AppModule } from './app.module'; import { AppComponent } from './app.component';
@NgModule({ imports: [ AppModule, ServerModule, ModuleMapLoaderModule ], providers: [ // Add universal-only providers here ], bootstrap: [ AppComponent ] })
export class AppServerModule {}
Come potete notare ,all’interno dell’array imports, il primo modulo importato è il modulo AppModule, ossia il modulo radice con all’interno definiti tutti i componenti e service da utilizzare per il funzionamento della nostra app.
AGGIUNGERE IL FILE MAIN.SERVER.TS
Aprite il file main.server.ts creato in precedenza ed aggiungete tali righe:
export { AppServerModule } from './app/app.server.module';
In pratica questo si occuperà di creare tutti i file all’interno del server e dovrà essere inserito all’interno del file di configurazione di ANGULAR CLI (angular.json) al posto del classico file main.ts.
IMPOSTARE IL WEB SERVER UTILIZZANDO EXPRESS
Ipotizzando di utilizzare un web server che sfrutti NODE ed EXPRESS creiamo un file all’interno della root principale del progetto e lo chiamiamo server.ts (root/server.ts), quindi inserite:
import 'zone.js/dist/zone-node'; import 'reflect-metadata'; import { enableProdMode } from '@angular/core'; import * as express from 'express'; import { join } from 'path';
enableProdMode();
// Server Express const app = express(); const PORT = process.env.PORT || 4000; const DIST_FOLDER = join(process.cwd(), 'dist');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
// Express Engine import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] }));
app.set('view engine', 'html'); app.set('views', join(DIST_FOLDER, 'browser'));
// Gestisco tutte le richieste alle risorse statiche prelevandole dalla cartella browser app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// Gestisco tutte le richieste di navigazione interne all’app app.get('*', (req, res) => { res.render('index', { req }); });
// Start up the Node server app.listen(PORT, () => { console.log(`Node server in ascolto su http://localhost:${PORT}`); });
CREARE UN FILE DI CONFIGURAZIONE PER LA COMPILAZIONE
All’iterno della cartella src create il file tsconfig.server.json (root/src/tsconfig.server.json) il quale configurerà TypeScript per la corretta compilazione , esso conterrà l’informazione legata al punto di ingresso dell’applicazione specificando il modulo server realizzato in precedenza:
{ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "baseUrl": "./", "module": "commonjs", "types": [] }, "exclude": [ "test.ts", "**/*.spec.ts" ], "angularCompilerOptions": { "entryModule": "app/app.server.module#AppServerModule" } }
COMPILAZIONE DA TYPESCRIPT A JAVASCRIPT
Ora sarà necessario creare il file che si occuperà della compilazione di file TypeScript in JavaScript compatibile per i browser. Quindi all’interno della root principale create il file webpack.server.config.js (root/webpack.server.config.js) ed inserite le seguenti righe:
const path = require('path'); const webpack = require('webpack');
module.exports = { entry: { server: './server.ts' }, resolve: { extensions: ['.js', '.ts'] }, target: 'node', mode: 'none', // this makes sure we include node_modules and other 3rd party libraries externals: [/node_modules/], output: { path: path.join(__dirname, 'dist'),
filename: '[name].js' }, module: { rules: [{ test: /.ts$/, loader: 'ts-loader' }] },
plugins: [ // Temporary Fix for issue: https://github.com/angular/angular/issues/11580 // for 'WARNING Critical dependency: the request of a dependency is an expression' new webpack.ContextReplacementPlugin( /(.+)?angular(|)core(.+)?/, path.join(__dirname, 'src'), // location of your src {} // a map of your routes ),
new webpack.ContextReplacementPlugin( /(.+)?express(|)(.+)?/, path.join(__dirname, 'src'), {} ) ] };
CONFIGURARE ANGULAR CLI
All’interno del file di configurazione di Angular Cli ,cioè angular.json ,sono presenti le righe per costruire l’applicazione in funzione di sviluppo e produzione, Quindi aggiungiamo quelle relative a costruire l’applicazione lato server:
"architect": {
...
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
}
}
...
}
COSTRUZIONE E LANCIO DELL’APPLICAZIONE
Prima di lanciare la nostra applicazione dobbiamo configurare npm ed inserire le righe ,sotto, all’interno del file package.json per poter in seguito sfruttare i comandi indicati:
"scripts": {
...
"build:ssr":"npm run build:client-and-server-bundles && npm run webpack:server", "serve:ssr":"node dist/server.js", "build:client-and-server-bundles":"ng build --prod && ng run appizza:server", "webpack:server":"webpack --config webpack.server.config.js --progress --colors" ...
a)COSTRUISCO L’APPLICAZIONE
npm run build:ssr
Digitate le seguenti righe nel terminale, Angular Cli compila e raggruppa l’App Universale in due diverse cartelle: Browser e Server.
Webpack provvede nella compilazione del file server.ts in JavaScript.
b)ESEGUO L’APPLICAZIONE VERIFICANDONE IL FUNZIONAMENTO IN LOCALE
npm run serve:ssr
CONCLUSIONE
Il passaggio ad Angular Universal indubbiamente offre tanti vantaggi in termini di prestazioni. Comunque la sua configurazione è molto macchinosa ed inoltre va adattata in relazione al web server sfruttato. Quindi se dovete realizzare applicativi di modeste dimensioni pensateci bene prima di utilizzarla.