LocalStorage не определен (Angular Universal)
Я использую 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, которая использует Dependency Injection для решения этой проблемы. Но эта демонстрация действительно старая.
Скажем, у меня есть эти два файла:
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)
/мой-проект/node_modules/@angular/core/src/di/reflective_provider.js:240 throw new reflective_exceptions_1.NoAnnotationError(typeOrFunc, params); ^ Ошибка: не удается разрешить все параметры для "UserService" (Http,?). Убедитесь, что все параметры украшены вводом или имеют действительные аннотации типов и что "UserService" украшен Вводимый.
Терминал (просмотр в режиме npm)
ошибка 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: this.localStorage.getItem не является функцией
Ответы
Ответ 1
Обновление для более новых версий Angular
OpaqueToken
был заменен InjectionToken
который работает практически таким же образом, за исключением того, что имеет универсальный интерфейс InjectionToken<T>
который обеспечивает лучшую проверку типов и вывод.
Оригинальный ответ
Две вещи:
- Вы не внедряете какой-либо объект, который содержит объект localStorage, вы пытаетесь получить к нему прямой доступ как глобальный. Любой глобальный доступ должен быть первым признаком того, что что-то не так.
- В nodejs нет window.localStorage.
Вам нужно добавить адаптер для 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');
...
};
}
Ответ 2
В 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
- функция не нарушит ваш код - если вам не нужно применять совершенно разные app.module.shared.ts
поведения для сборок сервера и клиента; если это так, то было бы разумнее вместо этого реализовать собственную фабрику классов, как это было показано в других ответах.
В любом случае, как только вы закончите реализацию провайдера, вы можете внедрить универсальный LOCALSTORAGE
в любой компонент Angular и проверить тип платформы с помощью функции 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
- это то, чему можно доверять при разработке.
Для получения дополнительной информации, проверьте этот пост в блоге, который я написал на эту тему.
Ответ 3
Я не думаю, что это хорошее решение, но я столкнулся с той же проблемой, используя генератор aspnetcore-spa и решил его следующим образом:
@Injectable()
export class UserService {
foo() {}
bar() {}
loadCurrentUser() {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('token');
}
// do other things
};
}
Это условие предотвращает запуск клиентского кода на стороне сервера, где объект "окна" не существует.
Ответ 4
У меня есть аналогичная проблема с Angular 4 + Universal после шаги здесь, чтобы настроить SPA, который может отображать на стороне клиента или сервер.
Я использую oidc-client, потому что мне нужен мой SPA для работы в качестве клиента OpenId Connect/Oauth2 для моего сервера удостоверений.
Дело в том, что у меня была типичная проблема, когда localStorage или sessionStorage не определены на стороне сервера (они существуют только там, где есть объект окна, поэтому для узлов не было смысла иметь эти объекты).
Я безуспешно пытался подходить к подделке 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 в браузер, поэтому, даже если я запускаю свое приложение в NodeJ, в конечном итоге мой браузер печатает следующее:
Window is: object
Я знаю, что это не правильный ответ тем, кто действительно нуждается в доступе к localStorage или sessionStorage на стороне сервера, но для большинства случаев мы используем Angular Universal просто для рендеринга, который можно отобразить на стороне сервера, и для отправки вещей, которые не могут быть обработаны браузером для нормальной работы.
Ответ 5
Спасибо за @Martin отличную помощь. Но есть несколько мест ниже, которые нужно обновить, чтобы заставить их работать:
-
constructor
в user.service.ts
-
useValue
в main.node.ts, main.browser.ts
Вот как выглядят мои коды сейчас.
Я бы с удовольствием принял @Martin ответ, когда он обновил.
Кстати, я нашел a 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
Так мы имеем это в нашем проекте, как из этого 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')
}
}
}
Ответ 7
У меня нет достаточных знаний о подготовке приложений angular для запуска серверов. Но в подобном сценарии для реакции и nodejs, что нужно сделать, это сообщить серверу, что localStorage
. Например:
//Stub for localStorage
(global as any).localStorage = {
getItem: function (key) {
return this[key];
},
setItem: function (key, value) {
this[key] = value;
}
};
Надеюсь, это может помочь вам.
Ответ 8
Каким бы странным ни казался этот подход, он работает, и мне не пришлось ничего делать из того, что предлагают другие ответы.
Шаг 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
моего localStorage
Ответ 9
LocalStorage не реализован на стороне сервера. Нам нужно выбрать запись в зависимости от кода платформы или использовать куки для сохранения и чтения данных. ngx-cookie
- лучшее решение (ngx-cookie
мне известно) для работы с cookie на сервере и в браузере. Вы можете написать расширенный класс Storage с помощью 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);
}
}
Ответ 10
Эти шаги решили мою проблему:
Шаг 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)
Ответ 11
Решена проблема "getItem undefined" в следующих строках:
if(window.localStorage){
return window.localStorage.getItem('user');
}
Ответ 12
Я также столкнулся с той же проблемой.
Вы можете писать внутри "isBrowser", импортируя инструкцию ниже.
import { isBrowser } from 'angular2-universal';