My angular application has universal set up and running, but server side rendering wasn't taking place. I got a suggestion and step by step tutorial on what to do, because apparently ssr wasn't working since I was using "window" and "localstorage". I had to replace them with the ones provided by @ng-toolkit/universal This is the service that I created
import { Injectable, Inject } from '@angular/core';
import { LOCAL_STORAGE } from '@ng-toolkit/universal';
@Injectable({
providedIn: 'root'
})
export class LocalService implements Storage {
constructor(@Inject(LOCAL_STORAGE) private localStorage: any) {}
get length(): number {
return this.localStorage.length;
}
clear(): void {
this.localStorage.clear();
}
getItem(key: string): any {
return this.localStorage.getItem(key);
}
key(index: number): string | null {
return this.localStorage.key(index);
}
removeItem(key: string): void {
this.localStorage.removeItem(key);
}
setItem(key: string, data: string): void {
this.localStorage.setItem(key, data);
}
[key: string]: any;
[index: number]: string;
}
After it got errors, I found that I needed to include it in app.module.ts as a provider like this
providers: [
{ provide: LOCAL_STORAGE, useFactory: () => window.localStorage }
]
Afterwards, in every component where I had localStorage, I imported the service and implemented it instead
import { LocalService } from '../services/local.service';
constructor(private localStorage: LocalService)
this.localStorage.setItem('lang', 'en');
this.localStorage.getItem('lang');
The application runs fine when using ng serve/npm start to run it client side, but when building the app with "npm run build:ssr" and serving it locally, I get the following error
Node Express server listening on http://localhost:4000
ERROR TypeError: Cannot read properties of undefined (reading 'getItem')
This is what my server.ts file looks like
import 'zone.js/dist/zone-node';
import * as express from 'express';
import { join } from 'path';
// Fix for non SSR modulеs
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const compression = require('compression');
const template = fs.readFileSync(path.join('.', 'dist/browser', 'index.html')).toString();
const win = domino.createWindow(template);
// tslint:disable-next-line:no-string-literal
global['window'] = win;
// tslint:disable-next-line:no-string-literal
global['document'] = win.document;
// tslint:disable-next-line:no-string-literal
global['DOMTokenList'] = win.DOMTokenList;
// tslint:disable-next-line:no-string-literal
global['Node'] = win.Node;
// tslint:disable-next-line:no-string-literal
global['Text'] = win.Text;
// tslint:disable-next-line:no-string-literal
global['HTMLElement'] = win.HTMLElement;
// tslint:disable-next-line:no-string-literal
global['navigator'] = win.navigator;
// tslint:disable-next-line:no-string-literal
global['MutationObserver'] = getMockMutationObserver();
function getMockMutationObserver() {
return class {
observe(node, options) { }
disconnect() { }
takeRecords() {
return [];
}
};
}
// End of Fix for non SSR modulеs
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main');
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
I looked for an answer but couldn't find it anywhere. If you can answer or know the issues I would highly appreciate it.
CodePudding user response:
Try:
docClient.get(checkIfRecipeExistsParams).promise().then(data => { ... })
CodePudding user response:
Instead of having the provider in your AppModule
, you're going to have to put the provider in the main.ts
:
const providers: StaticProvider[] = [
{ provide: LOCAL_STORAGE, useFactory: () => window.localStorage }
];
document.addEventListener('DOMContentLoaded', () => {
platformBrowserDynamic(providers).bootstrapModule(AppBrowserModule)
.catch(err => console.error(err));
});
Then you'll also need to provide it during SSR (main.server.ts):
export default createServerRenderer(params => {
const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports;
const providers: StaticProvider[] = [
{ provide: APP_BASE_HREF, useValue: params.baseUrl },
{ provide: LOCAL_STORAGE, useValue: null },
];
const options = {
document: params.data.originalHtml,
url: params.url,
extraProviders: providers
};
// Bypass ssr api call cert warnings in development
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const renderPromise = AppServerModuleNgFactory
? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
: /* dev */ renderModule(AppServerModule, options);
return renderPromise.then(html => ({ html }));
});
Don't forget to null-check the injectiontoken in your components. Anywhere you're injecting this LOCAL_STORAGE
provider:
class xxx {
constructor(@Inject(LOCAL_STORAGE) private localStorageService: LocalStorage | null) { }
...
}
and use it, you need to check if this field is not null
if (this.localStorageService) {
// Here you can use the service safely
...
}