localStorage не определен (угловой универсальный)

Я использую универсальный-стартер как костяк.

когда мой клиент запускается, он читает токен о пользовательской информации из localStorage.

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

  bar() {}

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

    // do other things
  };
}

все работает хорошо, однако я получил это в сервер (терминал) из-за сервера рендеринга:

исключение: ReferenceError: localStorage не определен

я получил идею от ng-conf-2016-универсальный-шаблоны что использование инъекции зависимостей для решения этой проблемы. Но эта демонстрация очень старая.

скажем, у меня есть эти два файла сейчас:

main.браузера.ТС

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

    UserService
  ]);
}

main.узел.ТС

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 бросьте новый reflective_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, но теперь я получил ошибку в терминале:

исключение: TypeError: этот.местный склад.getItem не является функцией

9 ответов


обновление для более новых версий Angular

OpaqueToken был заменен InjectionToken который работает примерно так же-за исключением того, что он имеет общий интерфейс InjectionToken<T> что делает для лучшей проверки типа и вывода.

Оригинальный Ответ!--9-->

две вещи:

  1. вы не вводите объект, содержащий объект localStorage, вы пытаетесь получить к нему прямой доступ как к глобальному. Любой глобальный доступ должно быть ясно, что что-то не так.
  2. нет окна.localStorage в nodejs.

что вам нужно сделать, это ввести адаптер для localStorage, который будет работать как для браузера, так и для NodeJS. Это также даст вам тестируемый код.

в местном хранилище.ТС:

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

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

в главном.браузер.ts мы введем фактический объект localStorage из Вашего браузера:

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

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

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

и затем в основном.узел.ts we будет использовать пустой объект:

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

Я не думаю, что это хорошее решение, но я столкнулся с той же проблемой, используя генератор aspnetcore-spa, и решил ее следующим образом:

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

  bar() {}

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

    // do other things
  };
}

это условие предотвращает запуск клиентского кода на стороне сервера, где объект "window" не существует.


в Angular 4 (и 5) Вы можете легко справиться с такой проблемой с помощью простой функции следующим образом:

app.модуль.ТС

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

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

если у вас есть разделенный файл сервера/клиента модуль на app.module.shared.ts файл - функция не будет нарушать ваш код-если вам не нужно применять совершенно другое поведение для серверных и клиентских сборок; если это так, было бы разумнее реализовать фабрику пользовательских классов вместо этого, как это было показано в других ответах.

в любом случае, как только вы закончите с реализацией поставщика, вы можете ввести LOCALSTORAGE generic в любом угловом компоненте и проверьте тип платформы с помощью 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.
        }
    }
}

стоит отметить, что после


у меня аналогичная проблема с Angular 4 + Universal following шаги чтобы настроить SPA, который может отображать на стороне клиента или на стороне сервера.

Я использую oidc-client потому что мне нужно, чтобы мой SPA действовал как клиент OpenId Connect/Oauth2 для моего сервера идентификации.

дело в том, что у меня была типичная проблема, когда localStorage или sessionStorage не определены на стороне сервера (они существуют только тогда, когда есть объект window, поэтому для 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 просто игнорирует выполнение/рендеринг на стороне сервера, когда нет объекта window, но это выполнение будет работать нормально, когда Angular Universal отправляет страницу с javascript в браузер, поэтому даже если я запускаю свое приложение в NodeJs в конце концов мой браузер печатает следующее: Window is: object

Я знаю, что это не правильный ответ для тех, кому действительно нужно получить доступ к localStorage или sessionStorage на стороне сервера, но для большинства случаев мы используем Angular Universal просто для рендеринга всего, что можно рендерить на стороне сервера, и для отправки вещей, которые не могут быть отображены в браузер, чтобы работать нормально.


Спасибо за большую помощь @Martin. Но есть несколько мест ниже, которые необходимо обновить, чтобы заставить его работать:

  • constructor на пользователей.услуга.ТС
  • useValue на main.узел.ТС, main.браузер.ТС

вот как выглядят мои коды сейчас.

Я хотел бы принять ответ @Martin, когда он обновляется.

кстати, я нашел import { LocalStorage } from 'angular2-universal';, но не знаю как. использовать это.

пользователей.услуга.ТС

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

местного хранилища.ТС

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

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

main.браузера.ТС

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

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

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

main.узел.ТС

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

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

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

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

вот как мы имеем это в нашем проекте, согласно этому 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')
        }
    }    
}

у меня нет достаточных знаний о подготовке угловых приложений для запуска serverside. Но в аналогичном сценарии для react & nodejs необходимо сообщить серверу, что localStorage есть. Например:

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

надеюсь, что это может помочь вам.


как глупо, как этот подход может показаться, он работает, и мне не пришлось делать ничего из сантехники, которые предлагают другие ответы.

Шаг 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 и все должно работать нормально.


работает ли вышеуказанный подход? Да, насколько я могу судить.

это лучше? Может, и нет!--7-->

какие-либо проблемы с производительностью? Насколько мне известно, нет. Просветить меня.

однако, как он стоит сейчас, это самый тупой, самый чистый подход к получить мой localStorage для передачи


Я также столкнулся с той же проблемой. Вы можете написать внутри проверки "isBrowser", импортировав оператор ниже.

import { isBrowser } from 'angular2-universal';