localStorage is not defined (Angular Universal)
Solution 1:
These steps resolved my issue:
Step 1: Run this command:
npm i localstorage-polyfill --save
Step 2: Add these two lines in server.ts file:
import 'localstorage-polyfill'
global['localStorage'] = localStorage;
Once you are done, run build command (eg: npm run build:serverless
)
All set now. Start the server again and you can see the issue is resolved.
Note: Use localStorage not window.localStorage, eg: localStorage.setItem(keyname, value)
Solution 2:
Update for newer versions of Angular
OpaqueToken
was superseded by InjectionToken
which works much in the same way -- except it has a generic interface InjectionToken<T>
which makes for better type checking and inference.
Orginal Answer
Two things:
- You are not injecting any object that contains the localStorage object, you are trying to access it directly as a global. Any global access should be the first clue that something is wrong.
- There is no window.localStorage in nodejs.
What you need to do is inject an adapter for localStorage that will work for both the browser and NodeJS. This will also give you testable code.
in local-storage.ts:
import { OpaqueToken } from '@angular/core';
export const LocalStorage = new OpaqueToken('localStorage');
In your main.browser.ts we will inject the actual localStorage object from your browser:
import {LocalStorage} from './local-storage.ts';
export function ngApp() {
return bootstrap(App, [
// ...
UserService,
{ provide: LocalStorage, useValue: window.localStorage}
]);
And then in main.node.ts we will use an empty object:
...
providers: [
// ...
UserService,
{provide: LocalStorage, useValue: {getItem() {} }}
]
...
Then your service injects this:
import { LocalStorage } from '../local-storage';
export class UserService {
constructor(@Inject(LocalStorage) private localStorage: LocalStorage) {}
loadCurrentUser() {
const token = this.localStorage.getItem('token');
...
};
}
Solution 3:
In Angular 4 (and 5) you can easily deal with such issue with a simple function in the following way:
app.module.ts
@NgModule({
providers: [
{ provide: 'LOCALSTORAGE', useFactory: getLocalStorage }
]
})
export class AppModule {
}
export function getLocalStorage() {
return (typeof window !== "undefined") ? window.localStorage : null;
}
If you have a server/client split file AppModule, place it in the app.module.shared.ts
file - the function won't break your code - unless you need to enforce completely different behaviours for the server and client builds; if that’s the case, it could be wiser to implement a custom class factory instead, just like it has been shown in other answers.
Anyway, once you're done with the provider implementation, you can inject the LOCALSTORAGE
generic in any Angular component and check for the platform type with the Angular-native isPlatformBrowser
function before using it:
import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
@Injectable()
export class SomeComponent {
constructor(
@Inject(PLATFORM_ID) private platformId: any,
@Inject('LOCALSTORAGE') private localStorage: any) {
// do something
}
NgOnInit() {
if (isPlatformBrowser(this.platformId)) {
// localStorage will be available: we can use it.
}
if (isPlatformServer(this.platformId)) {
// localStorage will be null.
}
}
}
It’s worth noting that, since the getLocalStorage()
function will return null
if the window object isn’t available, you could just check for this.localStorage
nullability and entirely skip the platform type check. However, I strongly recommend the above approach as that function implementation (and return value) might be subject to change in the future; conversely, the isPlatformBrowser
/ isPlatformServer
return values are something that can be trusted by design.
For more info, check out this blog post that I wrote on the topic.