localStorage is not defined (Angular Universal)

后端 未结 13 2282
情书的邮戳
情书的邮戳 2020-11-29 01:26

I am using universal-starter as backbone.

When my client starts, it read a token about user info from localStorage.

@Injectable()
export class UserSe         


        
相关标签:
13条回答
  • 2020-11-29 02:17

    As wierd as this approach might seem, it is working, and I had to do none of the plumbing the other answers are suggesting.

    Step 1

    Install localstorage-polyfill: https://github.com/capaj/localstorage-polyfill

    Step 2

    Assuming you followed this step: https://github.com/angular/angular-cli/wiki/stories-universal-rendering, you should have a file called, server.js in your project root folder.

    In this server.js add this:

    import 'localstorage-polyfill'
    
    global['localStorage'] = localStorage;
    

    Step 3

    Rebuild your project, npm run build:ssr, and all should work fine now.


    Does the above approach work? Yes, as far as I can tell.

    Is it the best? Maybe not

    Any performance issues? Not that I know of. Enlighten me.

    However, as it stands now, this is the dumbest, most cleanest approach to getting my localStorage to pass

    0 讨论(0)
  • 2020-11-29 02:18

    I am having a similar issue with Angular 4 + Universal following steps here to configure a SPA that can render at client side or server side.

    I am using oidc-client because I need my SPA to act as an OpenId Connect/Oauth2 client for my Identity Server.

    The thing is that I was having the typical problem where localStorage or sessionStorage are not defined in server side (they only exist when there's a window object, therefore it wouldn't make sense for nodeJs to have these objects).

    I have unsuccessfully tried the approach to mock the localStorage or sessionStorage and use the real one when in browser and an empty one in server.

    But I came to the conclusion that for my needs I don't really need localStorage or sessionStorage to do anything in server side. If executed in NodeJs, simply skip the part where sessionStorage or localStorage is used, and the execution will then happen at client-side.

    This would suffice:

    console.log('Window is: ' + typeof window);
        this.userManager = typeof window !== 'undefined'? new oidc.UserManager(config) : null; //just don't do anything unless there is a window object
    

    In client-side rendering it prints: Window is: object

    In nodeJs it prints: Window is: undefined

    The beauty of this is that Angular Universal will simply ignore the execution/rendering at server side when there is no window object, BUT that execution will be working fine when Angular Universal sends the page with javascript to the browser, therefore even if I am running my app in NodeJs eventually my browser prints the following: Window is: object

    I know this is not a proper answer for those who really need to access localStorage or sessionStorage in server side, but for most of the cases we use Angular Universal simply to render whatever is possible to render in server side, and for sending the things that can't be rendered to the browser to work normally.

    0 讨论(0)
  • 2020-11-29 02:18

    Thanks for @Martin's great help. But there are several places below need to be updated to get it work:

    • constructor in user.service.ts
    • useValue in main.node.ts, main.browser.ts

    This is how my codes look like now.

    I would love to accept @Martin's answer when he updated.

    BTW, I found a import { LocalStorage } from 'angular2-universal';, but not sure how to use that.

    user.service.ts

    import { Injectable, Inject } from '@angular/core';
    
    import { LocalStorage } from '../local-storage';
    
    @Injectable()
    export class UserService {
      constructor (
        @Inject(LocalStorage) private localStorage) {}
    
      loadCurrentUser() {
        const token = localStorage.getItem('token');
    
        // do other things
      };
    }
    

    local-storage.ts

    import { OpaqueToken } from '@angular/core';
    
    export const LocalStorage = new OpaqueToken('localStorage');
    

    main.broswer.ts

    import { LocalStorage } from './local-storage';
    
    export function ngApp() {
      return bootstrap(App, [
        // ...
    
        { provide: LocalStorage, useValue: window.localStorage},
        UserService
      ]);
    }
    

    main.node.ts

    import { LocalStorage } from './local-storage';
    
    export function ngApp(req, res) {
      const config: ExpressEngineConfig = {
        // ...
        providers: [
          // ...
    
          { provide: LocalStorage, useValue: { getItem() {} }},
          UserService
        ]
      };
    
      res.render('index', config);
    }
    
    0 讨论(0)
  • 2020-11-29 02:22

    I don't think this is a good solution, but I was stucked with the same problem using aspnetcore-spa generator and solved it this way:

    @Injectable()
    export class UserService {
      foo() {}
    
      bar() {}
    
      loadCurrentUser() {
        if (typeof window !== 'undefined') {
           const token = localStorage.getItem('token');
        }
    
        // do other things
      };
    }
    

    This condition prevents client code from running on the server-side where 'window' object doesn't exist.

    0 讨论(0)
  • 2020-11-29 02:22

    I have also ran in to the same issue. You can write inside 'isBrowser' check by importing below statement.

    import { isBrowser } from 'angular2-universal';

    0 讨论(0)
  • 2020-11-29 02:25

    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:

    1. 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.
    2. 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');
            ...
        };
    }
    
    0 讨论(0)
提交回复
热议问题