【问题标题】:localStorage is not defined (Angular Universal)localStorage 未定义(Angular Universal)
【发布时间】:2016-12-29 08:45:31
【问题描述】:

我使用universal-starter 作为主干。

当我的客户端启动时,它会从 localStorage 中读取有关用户信息的令牌。

@Injectable()
export class UserService {
  foo() {}

  bar() {}

  loadCurrentUser() {
    const token = localStorage.getItem('token');

    // do other things
  };
}

一切正常,但是由于服务器渲染,我在服务器端(终端)得到了这个:

异常:ReferenceError:localStorage 未定义

我从ng-conf-2016-universal-patterns 得到了使用依赖注入来解决这个问题的想法。但是那个演示真的很老了。

假设我现在有这两个文件:

ma​​in.broswer.ts

export function ngApp() {
  return bootstrap(App, [
    // ...

    UserService
  ]);
}

ma​​in.node.ts

export function ngApp(req, res) {
  const config: ExpressEngineConfig = {
    // ...
    providers: [
      // ...
      UserService
    ]
  };

  res.render('index', config);
}

现在他们使用相同的 UserService。谁能给出一些代码来解释如何使用不同的依赖注入来解决这个问题?

如果有其他更好的方法而不是依赖注入,那也很酷。


更新 1 我正在使用 Angular 2 RC4,我尝试了 @Martin 的方式。但即使我导入它,它仍然在下面的终端中给我错误:

终端(npm start)

/my-project/node_modules/@angular/core/src/di/reflective_provider.js:240 抛出新的reflective_exceptions_1.NoAnnotationError(typeOrFunc, params); ^ 错误:无法解析“UserService”(Http,?)的所有参数。确保所有参数都用 Inject 或 具有有效的类型注释,并且 'UserService' 装饰有 可注射。

终端(npm run watch)

错误 TS2304:找不到名称“LocalStorage”。

我猜它与angular2-universal 中的LocalStorage 以某种方式重复(虽然我没有使用import { LocalStorage } from 'angular2-universal';),但即使我尝试将我的更改为LocalStorage2,仍然无法正常工作。

同时,我的 IDE WebStorm 也显示为红色:

顺便说一句,我找到了import { LocalStorage } from 'angular2-universal';,但不知道如何使用它。


UPDATE 2,我改成(不知道有没有更好的办法):

import { Injectable, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { LocalStorage } from '../../local-storage';

@Injectable()
export class UserService {
  constructor (
    private _http: Http,
    @Inject(LocalStorage) private localStorage) {}  // <- this line is new

  loadCurrentUser() {
    const token = this.localStorage.getItem('token'); // here I change from `localStorage` to `this.localStorage`

    // …
  };
}

这解决了 UPADAT 1 中的问题,但现在我在终端出现错误:

异常:TypeError:this.localStorage.getItem 不是函数

【问题讨论】:

  • 这可能是因为旧的浏览器版本不支持本地和会话存储 afaik
  • 另外,隐私浏览中的 Safari 已禁用 localStorage - 在使用前应始终检查它是否存在。
  • 感谢您的帮助。它在客户端运行良好,现在我想在服务器端停止服务器渲染。
  • 您的 local-storage.ts 文件的导入路径是否正确?
  • @Martin 是的,我确信它是正确的,这就是为什么import { LocalStorage } from 'angular2-universal'; 显示灰色(灰色表示未使用)。如果路径错误,它也会显示红色

标签: angular typescript local-storage angular2-universal


【解决方案1】:

这些步骤解决了我的问题:

第 1 步: 运行此命令:

npm i localstorage-polyfill --save

第 2 步:server.ts 文件中添加这两行:

import 'localstorage-polyfill'

global['localStorage'] = localStorage;

完成后,运行构建命令(例如:npm run build:serverless

现在一切就绪。再次启动服务器,您可以看到问题已解决。

注意:使用localStorage而不是window.localStorage,例如:localStorage.setItem(keyname, value)

【讨论】:

  • 这个在 Angular 9 中为我工作。我无法让接受的答案工作,可能只是因为从那时起项目结构发生了变化
  • @Tomasz Chudzik 我尝试了上述解决方案,但仍然遇到同样的错误。除了上面提到的步骤之外,你还做了什么额外的事情吗?
  • 这仍然适用于 Angular 12
【解决方案2】:

更新 Angular 的新版本

OpaqueTokenInjectionToken 取代,它的工作方式大致相同——除了它有一个通用接口InjectionToken&lt;T&gt; 可以更好地进行类型检查和推理。

原始答案

两件事:

  1. 您没有注入任何包含 localStorage 对象的对象,而是尝试将其作为全局对象直接访问。任何全局访问都应该是出现问题的第一条线索。
  2. nodejs中没有window.localStorage。

您需要做的是为 localStorage 注入一个适配器,该适配器将同时适用于浏览器和 NodeJS。这也将为您提供可测试的代码。

在本地存储.ts 中:

import { OpaqueToken } from '@angular/core';

export const LocalStorage = new OpaqueToken('localStorage');

在您的 main.browser.ts 中,我们将从您的浏览器中注入实际的 localStorage 对象:

import {LocalStorage} from './local-storage.ts';

export function ngApp() {
  return bootstrap(App, [
    // ...

    UserService,
    { provide: LocalStorage, useValue: window.localStorage}
  ]);

然后在 main.node.ts 中我们将使用一个空对象:

... 
providers: [
    // ...
    UserService,
    {provide: LocalStorage, useValue: {getItem() {} }}
]
...

然后你的服务注入这个:

import { LocalStorage } from '../local-storage';

export class UserService {

    constructor(@Inject(LocalStorage) private localStorage: LocalStorage) {}

    loadCurrentUser() {

        const token = this.localStorage.getItem('token');
        ...
    };
}

【讨论】:

  • 嗨马丁,我应该把第一块代码放在哪里,因为我看到了providers。我不确定那个文件在哪里。
  • 这是一个很好的问题,我已经更新了我的答案来解决这个问题。
  • 我猜你的意思是{ provide: LocalStorage, useValue: {}},{ provide: LocalStorage, useValue: window.localStorage},{ provide: LocalStorage, useFactory: () =&gt; (window) ? window.localStorage : {}},。但即使我解决了这个问题,我仍然有问题,请检查我的问题更新部分。
  • @Martin 在 main.browser.ts 中写成 { provide: LocalStorage, useValue: window.localStorage} 意味着 LocalStorage 对象是参照 window.localStorage 创建的。在 main.node.ts 中写成 {provide: LocalStorage, useValue: {getItem() {} }} 意味着 LocalStorage 的创建是指什么都没有。在服务中,我们调用 this.localStorage.getItem('token') 意味着每当调用请求时,都会调用该服务来检查令牌。所以现在服务器需要拥有令牌的访问权限。但是我很困惑,那么它将如何获得访问权限?
  • 当您使用带有useValue 的提供程序时,它似乎创建了window.localStorage 的副本。我也使用 setItem,我不得不使用工厂 (useFactory) 来确保将更改存储到 window.localStorage,就像@darkseal answer中建议的那样
【解决方案3】:

在 Angular 4(和 5)中,您可以通过以下方式使用简单的函数轻松处理此类问题:

app.module.ts

@NgModule({
    providers: [
        { provide: 'LOCALSTORAGE', useFactory: getLocalStorage }
    ]
})
export class AppModule {
}

export function getLocalStorage() {
    return (typeof window !== "undefined") ? window.localStorage : null;
}

如果您有一个服务器/客户端拆分文件 AppModule,请将其放在 app.module.shared.ts 文件中 - 该函数不会破坏您的代码 - 除非您需要对服务器强制执行完全不同的行为和客户端构建;如果是这种情况,那么实现自定义类工厂可能会更明智,就像其他答案中显示的那样。

无论如何,一旦你完成了提供者的实现,你可以在任何 Angular 组件中注入 LOCALSTORAGE 泛型,并在使用之前使用 Angular-native isPlatformBrowser 函数检查平台类型:

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

值得注意的是,由于如果窗口对象不可用,getLocalStorage() 函数将返回 null,因此您可以只检查 this.localStorage 可空性并完全跳过平台类型检查。但是,我强烈推荐上述方法,因为该函数实现(和返回值)将来可能会发生变化;相反,isPlatformBrowser / isPlatformServer 返回值是可以被设计信任的。

有关更多信息,请查看我就该主题撰写的 this blog post

【讨论】:

    【解决方案4】:

    我在使用 steps here 配置可以在客户端或服务器端呈现的 SPA 时遇到了与 Angular 4 + Universal 类似的问题。

    我正在使用oidc-client,因为我需要我的 SPA 充当身份服务器的 OpenId Connect/Oauth2 客户端。

    问题是我遇到了一个典型的问题,即 localStorage 或 sessionStorage 未在服务器端定义(它们仅在有窗口对象时存在,因此 nodeJs 拥有这些对象没有意义)。

    我没有成功尝试模拟 localStorage 或 sessionStorage 的方法,并在浏览器中使用真实的,在服务器中使用空的。

    但我得出的结论是,出于我的需要,我真的不需要 localStorage 或 sessionStorage 在服务器端做任何事情。如果在 NodeJs 中执行,只需跳过使用 sessionStorage 或 localStorage 的部分,然后执行将发生在客户端。

    这就足够了:

    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
    

    在客户端渲染中,它会打印: Window is: object

    在 nodeJs 中它打印: Window is: undefined

    这样做的好处是,当没有窗口对象时,Angular Universal 将简单地忽略服务器端的执行/渲染,但是当 Angular Universal 将带有 javascript 的页面发送到浏览器时,执行将正常工作,因此即使我在 NodeJs 中运行我的应用程序,最终我的浏览器打印以下内容: Window is: object

    我知道这对于那些真正需要在服务器端访问 localStorage 或 sessionStorage 的人来说不是一个正确的答案,但在大多数情况下,我们使用 Angular Universal 只是为了呈现任何可能在服务器端呈现的内容,并用于发送无法渲染到浏览器正常工作的东西。

    【讨论】:

    • 正是我试图实现的+1
    【解决方案5】:

    尽管这种方法看起来很奇怪,但它确实有效,而且我不必做其他答案所暗示的任何工作。

    第 1 步

    安装localstorage-polyfill:https://github.com/capaj/localstorage-polyfill

    第 2 步

    假设您按照以下步骤操作:https://github.com/angular/angular-cli/wiki/stories-universal-rendering,您的项目根文件夹中应该有一个名为 server.js 的文件。

    在这个server.js添加这个:

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

    第 3 步

    重建你的项目,npm run build:ssr,现在一切都应该可以正常工作了。


    上述方法有效吗?是的,据我所知。

    这是最好的吗?也许不会

    任何性能问题?从来没听说过。启发我。

    但是,就目前而言,这是让我的localStorage 通过的最愚蠢、最干净的方法

    【讨论】:

    • 这适用于检查元素而不是查看源。有什么解决办法吗?
    • @subrat71 视图源不是网站的渲染版本。检查元素是渲染的版本。
    【解决方案6】:

    您可以使用PLATFORM_ID令牌来检查当前平台是浏览器还是服务器。

    import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
    import { isPlatformBrowser } from '@angular/common';
    
    @Component({
      selector: "app-root",
      templateUrl: "./app.component.html"
    })
    export class AppComponent implements OnInit {
    
       constructor(@Inject(PLATFORM_ID) private platformId: Object) {  }
    
       ngOnInit() {
    
         // Client only code.
         if (isPlatformBrowser(this.platformId)) {
            let item = {key1: 'value1', key2: 'valu2' };
            localStorage.setItem( itemIndex, JSON.stringify(item) );
         }
    
       }
     }
    

    【讨论】:

      【解决方案7】:

      这就是我们在项目中使用它的方式,来自这个 github comment

      import { OnInit, PLATFORM_ID, Inject } from '@angular/core';
      import { isPlatformServer, isPlatformBrowser } from '@angular/common';
      
      export class SomeClass implements OnInit {
      
          constructor(@Inject(PLATFORM_ID) private platformId: Object) { }
      
          ngOnInit() {
              if (isPlatformServer(this.platformId)) {
                  // do server side stuff
              }
      
              if (isPlatformBrowser(this.platformId)) {
                 localStorage.setItem('myCats', 'Lissie & Lucky')
              }
          }    
      }
      

      【讨论】:

        【解决方案8】:

        我认为这不是一个好的解决方案,但我在使用 aspnetcore-spa 生成器时遇到了同样的问题并以这种方式解决了它:

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

        这种情况会阻止客户端代码在不存在“窗口”对象的服务器端运行。

        【讨论】:

        • 谢谢谢谢谢谢。这是我在 2.2.4 中唯一有效的解决方案。
        【解决方案9】:

        感谢@Martin 的大力帮助。但是下面有几个地方需要更新才能正常工作:

        • constructoruser.service.ts
        • useValuema​​in.node.tsma​​in.browser.ts

        这就是我的代码现在的样子。

        当@Martin 更新时,我很乐意接受他的回答。

        顺便说一句,我找到了import { LocalStorage } from 'angular2-universal';,但是 不知道怎么用。

        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');
        

        ma​​in.broswer.ts

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

        ma​​in.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);
        }
        

        【讨论】:

        • 我已经更新了我的答案。我很高兴我能把你带到那里。
        • 我在服务中遇到错误auth-token.service.ts (15,43): Cannot find name 'Inject'. 可能是什么问题?
        【解决方案10】:

        LocalStorage 未由服务器端实现。我们需要选择编写依赖于平台的代码或使用 cookie 来保存和读取数据。 ngx-cookie 是在服务器和浏览器上使用 cookie 的最佳解决方案(据我所知)。 您可以使用 cookie 编写扩展 Storage 类: universal.storage.ts

        import { Injectable } from '@angular/core';
        import { CookieService } from 'ngx-cookie';
        
        @Injectable()
        export class UniversalStorage implements Storage {
          [index: number]: string;
          [key: string]: any;
          length: number;
          cookies: any;
        
          constructor(private cookieService: CookieService) {}
        
          public clear(): void {
            this.cookieService.removeAll();
          }
        
          public getItem(key: string): string {
            return this.cookieService.get(key);
          }
        
          public key(index: number): string {
            return this.cookieService.getAll().propertyIsEnumerable[index];
          }
        
          public removeItem(key: string): void {
            this.cookieService.remove(key);
          }
        
          public setItem(key: string, data: string): void {
            this.cookieService.put(key, data);
          }
        }
        

        【讨论】:

          【解决方案11】:

          通过以下几行解决了“getItem undefined”问题:

          if(window.localStorage){
                return window.localStorage.getItem('user');
          }
          

          【讨论】:

            【解决方案12】:

            我没有足够的知识准备 Angular 应用程序来运行服务器端。但是在 react & nodejs 的类似场景中,需要做的是让服务器知道localStorage 是什么。例如:

            //Stub for localStorage
            (global as any).localStorage = {
              getItem: function (key) {
                return this[key];
              },
              setItem: function (key, value) {
                this[key] = value;
              }
            };
            

            希望对你有所帮助。

            【讨论】:

              【解决方案13】:

              我也遇到了同样的问题。 您可以通过导入以下语句在“isBrowser”检查中编写。

              import { isBrowser } from 'angular2-universal';

              【讨论】:

              • 请详细说明你的答案,上面两行什么都学不到。
              • 请填写您的答案
              • isBrowser 是 Angular 通用模块的一部分,如果应用程序在浏览器中运行,则为 true,如果在节点或服务器中运行,则为 false。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-04-21
              • 2020-11-12
              • 2018-09-25
              • 2020-08-05
              • 1970-01-01
              • 2020-02-15
              • 2020-05-28
              相关资源
              最近更新 更多