niversal-starter をバックボーンとして使用しています。
クライアントが起動すると、localStorageからユーザー情報に関するトークンを読み取ります。
@Injectable()
export class UserService {
foo() {}
bar() {}
loadCurrentUser() {
const token = localStorage.getItem('token');
// do other things
};
}
すべてがうまく機能しますが、サーバーレンダリングのためにサーバー側でこれを取得しました(ターミナル):
例外:ReferenceError:localStorageが定義されていません
ng-conf-2016-universal-patterns からアイデアを得ました。これは、依存性注入を使用してこれを解決するというものです。しかし、そのデモは本当に古いものです。
今、これらの2つのファイルがあるとします。
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を両方使用しています。誰かがこれを解決するために異なる依存性注入を使用する方法を説明するコードを提供できますか?
依存性注入よりも別のより良い方法があれば、それもクールです。
UPDATE 1Angular 2 RC4、@ Martinの方法を試しました。しかし、それをインポートしても、まだ以下の端末でエラーが発生します:
端末(npm start)
/my-project/node_modules/@angular/core/src/di/reflective_provider.js:240 throw new Reflective_exceptions_1.NoAnnotationError(typeOrFunc、params); ^エラー:「UserService」(Http 、?)のすべてのパラメーターを解決できません。すべてのパラメーターがInjectで装飾されているか、有効な型注釈があること、および 'UserService'がInjectableで装飾されていることを確認してください。
端末(npm run watch)
エラーTS2304:名前 'LocalStorage'が見つかりません。
angular2-universal
のLocalStorage
と何らかの形で複製されていると思います(import { LocalStorage } from 'angular2-universal';
を使用していませんが)、私のものをLocalStorage2
に変更しようとしても、まだ動作しません。
そしてその間、my IDE WebStormは赤も表示します:
ところで、私はimport { LocalStorage } from 'angular2-universal';
を見つけましたが、それをどのように使うべきかわかりません。
UPDATE 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は関数ではありません
Angularの新しいバージョンの更新
OpaqueToken
はInjectionToken
に取って代わられましたが、これはほとんど同じ方法で機能しますが、汎用インターフェースInjectionToken<T>
これにより、型チェックと推論が改善されます。
元の回答
2つのこと:
必要なのは、ブラウザとNodeJSの両方で機能するlocalStorageのアダプターを挿入することです。これにより、テスト可能なコードも得られます。
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');
...
};
}
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
_ nullabilityをチェックし、プラットフォームタイプチェックを完全にスキップできることに注意してください。 。ただし、関数の実装(および戻り値)は将来変更される可能性があるため、上記のアプローチを強くお勧めします。逆に、isPlatformBrowser
/isPlatformServer
の戻り値は、設計上信頼できるものです。
詳細については、このトピックで書いた このブログ投稿 をご覧ください。
Angular 4 +ユニバーサル ステップのここ で同様の問題が発生し、クライアント側またはサーバー側でレンダリングできるSPAを構成しています。
oidc-client を使用しています。これは、SPAがIdentity ServerのOpenId Connect/Oauth2クライアントとして機能する必要があるためです。
問題は、サーバー側でlocalStorageまたはsessionStorageが定義されていないという典型的な問題を抱えていたことです(ウィンドウオブジェクトがある場合にのみ存在するため、nodeJがこれらのオブジェクトを持つことは意味がありません)。
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を使用してブラウザーに接続するため、NodeJsでアプリを実行している場合でも、最終的にブラウザーは次を出力します:Window is: object
これは、サーバー側でlocalStorageまたはsessionStorageに実際にアクセスする必要がある人には適切な答えではないことを知っていますが、ほとんどの場合、サーバー側でレンダリング可能なものをすべてレンダリングするためにAngular Universalを使用し、レンダリングできないものをブラウザに送信して正常に動作させるため。
これは良い解決策ではないと思いますが、aspnetcore-spaジェネレーターを使用して同じ問題に悩まされ、次のように解決しました。
@Injectable()
export class UserService {
foo() {}
bar() {}
loadCurrentUser() {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('token');
}
// do other things
};
}
この条件により、「window」オブジェクトが存在しないサーバー側でクライアントコードが実行されなくなります。
@Martinの大きな助けに感謝します。ただし、以下のいくつかの場所を更新する必要があります。
constructor
in ser.service.tsuseValue
in main.node.ts、main.browser.tsこれは私のコードがどのように見えるかです。
彼が更新したとき、@ Martinの答えを受け入れたいです。
ところで、私は
import { LocalStorage } from 'angular2-universal';
を見つけましたが、それをどのように使うべきかわかりません。
ser.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);
}
これは、この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')
}
}
}
このアプローチは奇妙に思えるかもしれませんが、機能しているので、他の答えが示唆している配管作業は一切しませんでした。
ステップ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はサーバー側で実装されていません。プラットフォームコードに依存して書き込むか、データの保存と読み取りにCookieを使用するかを選択する必要があります。 ngx-cookie
は、サーバーとブラウザーでCookieを処理するための最良のソリューション(知っている)です。 cookie workで拡張Storageクラスを書くことができます: niversal.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);
}
}
angularアプリをサーバーサイドで実行するための準備について十分な知識がありません。しかし、reactとnodejsの同様のシナリオでは、サーバーにlocalStorage
が何であるかを知らせる必要があります。たとえば:
//Stub for localStorage
(global as any).localStorage = {
getItem: function (key) {
return this[key];
},
setItem: function (key, value) {
this[key] = value;
}
};
これがあなたの助けになることを願っています。
次の手順で問題が解決しました。
ステップ1:このコマンドを実行します:
_npm i localstorage-polyfill --save
_
ステップ2:server.tsファイルに次の2行を追加します。
_import 'localstorage-polyfill'
global['localStorage'] = localStorage;
_
完了したら、ビルドコマンドを実行します(例:_npm run build:serverless
_)
すべての設定が完了しました。サーバーを再起動すると、問題が解決したことがわかります。
注: window.localStorageではなくlocalStorageを使用します。例:localStorage.setItem(keyname, value)
以下の行で「getItem undefined」の問題を解決しました。
if(window.localStorage){
return window.localStorage.getItem('user');
}