localStorage未定义(Angular Universal)

时间:2016-08-22 17:43:09

标签: angular typescript local-storage angular2-universal

我使用universal-starter作为主干。

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

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

  bar() {}

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

    // do other things
  };
}

一切运作良好,但由于服务器呈现,我在服务器端(终端)得到了这个:

  

EXCEPTION:ReferenceError:未定义localStorage

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

说我现在有这两个文件:

main.broswer.ts

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

    UserService
  ]);
}

main.node.ts

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

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

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

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

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

终端(npm开始)

  

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

终端(npm run watch)

  

错误TS2304:找不到名称'LocalStorage'。

我想它与LocalStorage中的angular2-universal有些重复(虽然我没有使用import { LocalStorage } from 'angular2-universal';),但即使我尝试将我的更改为LocalStorage2,不行。

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

enter image description here

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

更新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 中的问题,但现在终端出错:

  

EXCEPTION:TypeError:this.localStorage.getItem不是函数

13 个答案:

答案 0 :(得分:19)

更新版本的Angular

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

原始回答

两件事:

  1. 您没有注入包含localStorage对象的任何对象,而是尝试直接以全局方式访问它。任何全球访问都应该是出现问题的第一个线索。
  2. nodejs中没有window.localStorage。
  3. 您需要做的是为localStorage注入一个适用于浏览器和NodeJS的适配器。这也将为您提供可测试的代码。

    在local-storage.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');
            ...
        };
    }
    

答案 1 :(得分: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组件中注入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

答案 2 :(得分:3)

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

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

  bar() {}

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

    // do other things
  };
}

此条件阻止客户端代码在服务器端运行,其中“window”对象不存在。

答案 3 :(得分:3)

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

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

问题在于我遇到了典型的问题,即localStorage或sessionStorage没有在服务器端定义(它们只存在于窗口对象中,因此nodeJs有这些没有意义)对象)。

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

但我得出结论,根据我的需要,我并不需要localStorage或sessionStorage在服务器端做任何事情。如果在NodeJ中执行,只需跳过使用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来渲染服务器端可能呈现的内容,以及发送那些无法呈现给浏览器以正常工作的东西。

答案 4 :(得分:3)

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

   }
 }

答案 5 :(得分:2)

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

    user.service.ts
  • constructor main.node.ts 中的
  • useValue main.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
  };
}

本地-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);
}

答案 6 :(得分:2)

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

步骤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)

答案 7 :(得分:1)

这就是我们在项目中拥有它的方式,根据这个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 :(得分:1)

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

let length = 15;// Whatever you want
for(let i=0;i<length;i++){
  if(this.obj['row'+i] === undefined){
    this.obj['row'+i] = [];
  }
}

答案 9 :(得分:0)

我没有足够的知识来准备角度应用程序来运行服务器端。但在反应和类似的情况下nodejs,需要做的是让服务器知道localStorage是什么。例如:

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

希望这对你有任何帮助。

答案 10 :(得分:0)

服务器端未实现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);
  }
}

答案 11 :(得分:-1)

这种方法可能看起来很奇怪,它正在发挥作用,我不得不做其他答案所暗示的管道工程。

第1步

安装localstorage-polyfillhttps://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通过的最愚蠢,最干净的方法

答案 12 :(得分:-2)

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

import { isBrowser } from 'angular2-universal';