Home > front end >  Angular Universal not working with LOCAL_STORAGE from @ng-toolkit/universal
Angular Universal not working with LOCAL_STORAGE from @ng-toolkit/universal

Time:07-25

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
  ...
}
  • Related