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 также показывает красный:
кстати, я нашел 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-->
две вещи:
- вы не вводите объект, содержащий объект localStorage, вы пытаетесь получить к нему прямой доступ как к глобальному. Любой глобальный доступ должно быть ясно, что что-то не так.
- нет окна.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';