36

我正在使用通用启动器作为主干。

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

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

  bar() {}

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

    // do other things
  };
}

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

例外:ReferenceError:localStorage 未定义

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

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

主浏览器.ts

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

    UserService
  ]);
}

主节点.ts

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

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

现在他们使用相同的用户服务。有人可以给出一些代码来解释如何使用不同的依赖注入来解决这个问题吗?

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


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

终端(npm 开始)

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

终端(npm 运行观察)

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

我猜它在某种程度上与LocalStoragefrom重复angular2-universal(虽然我没有使用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 不是函数

4

13 回答 13

35

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

第 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)

于 2019-09-04T05:15:58.867 回答
29

更新新版本的 Angular

OpaqueToken被它InjectionToken以相同的方式工作所取代——除了它有一个通用接口InjectionToken<T>,可以更好地进行类型检查和推理。

原始答案

两件事情:

  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');
        ...
    };
}
于 2016-08-23T10:32:25.047 回答
8

在 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 组件中注入泛型,并在使用它之前使用 Angular-native 函数LOCALSTORAGE检查平台类型: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()函数将返回,因此您可以只检查可空性并完全跳过平台类型检查。但是,我强烈推荐上述方法,因为该函数实现(和返回值)将来可能会发生变化;相反, /返回值是可以通过设计信任的。nullthis.localStorageisPlatformBrowserisPlatformServer

有关更多信息,请查看我就该主题撰写的这篇博文。

于 2017-11-20T00:12:55.190 回答
5

您可以使用 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) );
     }

   }
 }
于 2020-02-12T07:43:29.827 回答
5

我在 Angular 4 + Universal 上遇到了类似的问题,按照此处的步骤配置可以在客户端或服务器端呈现的 SPA。

我正在使用oidc-client,因为我需要我的 SPA 作为我的 Identity Server 的 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 只是为了渲染任何可能在服务器端渲染的东西,并发送那些无法渲染到浏览器正常工作。

于 2017-06-05T13:47:20.467 回答
5

尽管这种方法看起来很奇怪,但它正在起作用,而且我不必做其他答案所暗示的任何管道。

步骤1

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

第2步

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

在此server.js添加:

import 'localstorage-polyfill'

global['localStorage'] = localStorage;

第 3 步

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


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

它是最好的吗?也许不吧

有性能问题吗?从来没听说过。开导我。

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

于 2017-12-18T20:02:20.560 回答
4

根据这个 github评论,这就是我们在项目中使用它的方式:

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')
        }
    }    
}
于 2017-10-30T13:49:08.460 回答
3

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

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

  bar() {}

  loadCurrentUser() {
    if (typeof window !== 'undefined') {
       const token = localStorage.getItem('token');
    }

    // do other things
  };
}

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

于 2016-09-22T11:50:41.777 回答
2

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

  • constructoruser.service.ts
  • useValuemain.node.tsmain.browser.ts

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

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

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

用户服务.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
  };
}

本地存储.ts

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

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

主浏览器.ts

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

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

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

主节点.ts

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

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

      { provide: LocalStorage, useValue: { getItem() {} }},
      UserService
    ]
  };

  res.render('index', config);
}
于 2016-08-23T18:39:27.897 回答
1

服务器端未实现 LocalStorage。我们需要选择编写依赖于平台的代码或者使用cookie来保存和读取数据。 ngx-cookie是在服务器和浏览器上使用 cookie 的最佳解决方案(我知道)。您可以使用 cookie 工作编写扩展存储类: 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);
  }
}
于 2019-01-14T08:42:36.907 回答
1

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

if(window.localStorage){
      return window.localStorage.getItem('user');
}
于 2019-09-06T06:53:49.363 回答
0

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

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

希望这对您有任何帮助。

于 2016-08-23T09:56:04.253 回答
-2

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

import { isBrowser } from 'angular2-universal';

于 2017-02-15T16:01:51.447 回答