page - title angular
El almacenamiento local no está definido(Angular Universal) (10)
Almacenamiento local no implementado por el lado del servidor. Debemos elegir escribir dependiendo del código de la plataforma o usar una cookie para guardar y leer datos. ngx-cookie
es la mejor solución (que yo sepa) para trabajar con cookies en el servidor y el navegador. Puede escribir extender la clase de almacenamiento con el trabajo de cookies: 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);
}
}
Estoy usando universal-starter como columna vertebral.
Cuando mi cliente se inicia, lee un token sobre información de usuario de localStorage.
@Injectable()
export class UserService {
foo() {}
bar() {}
loadCurrentUser() {
const token = localStorage.getItem(''token'');
// do other things
};
}
Todo funciona bien, sin embargo, obtuve esto en el lado del servidor (terminal) debido a la representación del servidor:
EXCEPCIÓN: ReferenceError: localStorage no está definido
Obtuve la idea de ng-conf-2016-universal-patterns que usar Inyección de dependencia para resolver esto. Pero esa demo es realmente vieja.
Digamos que tengo estos dos archivos ahora:
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);
}
Ahora mismo usan ambos UserService. ¿Puede alguien dar algunos códigos para explicar cómo usar una inyección de dependencia diferente para resolver esto?
Si hay otra forma mejor que la inyección de dependencia, eso también será genial.
ACTUALIZACIÓN 1 Estoy usando Angular 2 RC4, lo intenté en el modo @ Martin. Pero incluso si lo importo, todavía me da un error en el siguiente terminal:
Terminal (inicio npm)
/my-project/node_modules/@angular/core/src/di/reflective_provider.js:240 lanza nuevo reflective_exceptions_1.NoAnnotationError (typeOrFunc, params); ^ Error: no se pueden resolver todos los parámetros para ''UserService'' (Http,?). Asegúrese de que todos los parámetros estén decorados con Inject o tengan anotaciones de tipo válidas y que ''UserService'' esté decorado con Injectable.
Terminal (npm run watch)
error TS2304: No se puede encontrar el nombre ''LocalStorage''.
Supongo que está duplicado de alguna manera con LocalStorage
de angular2-universal
(aunque no estoy usando import { LocalStorage } from ''angular2-universal'';
), pero incluso intenté cambiar el mío a LocalStorage2
, todavía no funciona.
Y mientras tanto, mi IDE WebStorm también muestra rojo:
Por cierto, encontré una import { LocalStorage } from ''angular2-universal'';
, pero no estoy seguro de cómo usar eso.
ACTUALIZACIÓN 2 , cambié a (no estoy seguro de si hay una mejor manera):
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`
// …
};
}
Esto resuelve el problema en UPADAT 1 , pero ahora tengo un error en el terminal:
EXCEPCIÓN: TypeError: this.localStorage.getItem no es una función
Así es como lo tenemos en nuestro proyecto, según este comment 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'')
}
}
}
En Angular 4 (y 5) puede tratar fácilmente este problema con una función simple de la siguiente manera:
app.module.ts
@NgModule({
providers: [
{ provide: ''LOCALSTORAGE'', useFactory: getLocalStorage }
]
})
export class AppModule {
}
export function getLocalStorage() {
return (typeof window !== "undefined") ? window.localStorage : null;
}
Si tiene un servidor de aplicaciones / archivos divididos cliente, colóquelo en el archivo app.module.shared.ts
(la función no romperá su código) a menos que necesite imponer comportamientos completamente diferentes para las compilaciones del servidor y del cliente; si ese es el caso, podría ser más inteligente implementar una fábrica de clases personalizada en su lugar, tal como se ha mostrado en otras respuestas.
De todos modos, una vez que haya terminado con la implementación del proveedor, puede inyectar el genérico LOCALSTORAGE
en cualquier componente angular y verificar el tipo de plataforma con la función isPlatformBrowser
Angular-native antes de usarla:
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.
}
}
}
Vale la pena tener en cuenta que, dado que la función getLocalStorage()
devolverá un null
si el objeto de la ventana no está disponible, puede verificar this.localStorage
nulabilidad de this.localStorage
y omitir por completo la comprobación del tipo de plataforma. Sin embargo, recomiendo encarecidamente el enfoque anterior, ya que la implementación de la función (y el valor de retorno) podrían estar sujetos a cambios en el futuro; a la inversa, los valores de retorno de isPlatformBrowser
/ isPlatformServer
son algo en lo que se puede confiar por diseño.
Para obtener más información, consulte esta publicación de blog que escribí sobre el tema.
Estoy teniendo un problema similar con Angular 4 + Universal siguiendo los pasos a continuación para configurar un SPA que se pueda procesar en el lado del cliente o del lado del servidor.
Estoy usando oidc-client porque necesito que mi SPA actúe como un cliente OpenId Connect / Oauth2 para mi Identity Server.
La cuestión es que estaba teniendo el problema típico de que localStorage o sessionStorage no están definidos en el lado del servidor (solo existen cuando hay un objeto de ventana, por lo tanto, no tendría sentido que los nodos tuvieran estos objetos).
He intentado sin éxito el enfoque para burlar el almacenamiento local o el almacenamiento de sesión y utilizar el real cuando está en el navegador y uno vacío en el servidor.
Pero llegué a la conclusión de que para mis necesidades realmente no necesito localStorage o sessionStorage para hacer nada en el lado del servidor. Si se ejecuta en NodeJs, simplemente omita la parte donde se utiliza sessionStorage o localStorage, y la ejecución se realizará en el lado del cliente.
Esto sería suficiente:
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
En la representación del lado del cliente se imprime: la Window is: object
En nodeJs se imprime: la Window is: undefined
La belleza de esto es que Angular Universal simplemente ignorará la ejecución / renderización en el lado del servidor cuando no haya un objeto de ventana, PERO esa ejecución funcionará bien cuando Angular Universal envíe la página con javascript al navegador, por lo tanto, incluso si estoy ejecutando Mi aplicación en NodeJs finalmente mi navegador imprime lo siguiente: La Window is: object
Sé que esta no es una respuesta adecuada para aquellos que realmente necesitan acceder a LocalStorage o sessionStorage en el lado del servidor, pero para la mayoría de los casos usamos Angular Universal simplemente para renderizar lo que sea posible para renderizar en el lado del servidor, y para enviar las cosas que No se puede renderizar al navegador para que funcione normalmente.
Gracias por la gran ayuda de @Martin. Pero hay varios lugares a continuación que deben actualizarse para que funcione:
-
constructor
en user.service.ts -
useValue
en main.node.ts , main.browser.ts
Así es como se ven mis códigos ahora.
Me encantaría aceptar la respuesta de @Martin cuando se actualice.
Por cierto, encontré una
import { LocalStorage } from ''angular2-universal'';
, pero no estoy seguro de cómo usar eso.
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
};
}
local-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);
}
No creo que esta sea una buena solución, pero tuve el mismo problema con el generador de aspnetcore-spa y lo resolví de esta manera:
@Injectable()
export class UserService {
foo() {}
bar() {}
loadCurrentUser() {
if (typeof window !== ''undefined'') {
const token = localStorage.getItem(''token'');
}
// do other things
};
}
Esta condición evita que el código del cliente se ejecute en el lado del servidor donde no existe el objeto ''ventana''.
No tengo suficiente conocimiento de cómo preparar aplicaciones angulares para ejecutar serveride. Pero en el escenario similar para reaccionar y nodejs, lo que hay que hacer es dejar que el servidor sepa qué es localStorage
. Por ejemplo:
//Stub for localStorage
(global as any).localStorage = {
getItem: function (key) {
return this[key];
},
setItem: function (key, value) {
this[key] = value;
}
};
Espero que esto pueda ser de alguna ayuda para usted.
Por extraño que pueda parecer este enfoque, está funcionando, y no tuve que hacer ninguna de las cañerías que sugieren las otras respuestas.
Paso 1
Instale localstorage-polyfill
: https://github.com/capaj/localstorage-polyfill
Paso 2
Suponiendo que haya seguido este paso: https://github.com/angular/angular-cli/wiki/stories-universal-rendering , debe tener un archivo llamado server.js
en su carpeta raíz del proyecto.
En este server.js
agregue esto:
import ''localstorage-polyfill''
global[''localStorage''] = localStorage;
Paso 3
Reconstruye tu proyecto, npm run build:ssr
, y todo debería funcionar bien ahora.
¿Funciona el enfoque anterior? Sí, por lo que puedo decir.
¿Es lo mejor? Tal vez no
¿Algún problema de rendimiento? No que yo sepa. Iluminame.
Sin embargo, tal como está ahora, este es el enfoque más tonto y más limpio para lograr que mi localStorage
pase.
También me he topado con el mismo problema. Puede escribir dentro de la verificación ''isBrowser'' importando la siguiente declaración.
import { isBrowser } from ''angular2-universal'';
Actualización para nuevas versiones de Angular
OpaqueToken
fue reemplazado por InjectionToken
que funciona de la misma manera, excepto que tiene una interfaz genérica InjectionToken<T>
que permite una mejor comprobación de tipos e inferencia.
Respuesta original
Dos cosas:
- No está inyectando ningún objeto que contenga el objeto localStorage, está intentando acceder a él directamente como global. Cualquier acceso global debe ser la primera pista de que algo está mal.
- No hay window.localStorage en nodejs.
Lo que debe hacer es inyectar un adaptador para localStorage que funcione tanto para el navegador como para NodeJS. Esto también le dará código comprobable.
en local-storage.ts:
import { OpaqueToken } from ''@angular/core'';
export const LocalStorage = new OpaqueToken(''localStorage'');
En su main.browser.ts inyectaremos el objeto localStorage real desde su navegador:
import {LocalStorage} from ''./local-storage.ts'';
export function ngApp() {
return bootstrap(App, [
// ...
UserService,
{ provide: LocalStorage, useValue: window.localStorage}
]);
Y luego en main.node.ts usaremos un objeto vacío:
...
providers: [
// ...
UserService,
{provide: LocalStorage, useValue: {getItem() {} }}
]
...
Entonces su servicio inyecta esto:
import { LocalStorage } from ''../local-storage'';
export class UserService {
constructor(@Inject(LocalStorage) private localStorage: LocalStorage) {}
loadCurrentUser() {
const token = this.localStorage.getItem(''token'');
...
};
}