在 Angular 應用開發中,我們在 TypeScript 程式碼裡呼叫 localStorage.
它透過 key 從 local storage 中檢索資料。 但是在伺服器上,此程式碼崩潰並顯示錯誤訊息:
ReferenceError: localStorage is undefined
在伺服器上執行 Angular 應用程式時,全域性空間中缺少標準瀏覽器 API.
例如,在伺服器端渲染模式下,開發人員不能像在客戶端渲染環境下那樣,直接訪問全域性文件物件。 要獲得對文件的引用,必須使用 DOCUMENT 令牌和 Angular 依賴注入機制 DI.
不要透過全域性空間使用瀏覽器 API,而是透過 DI 來替換或停用瀏覽器實現,以便在伺服器上安全使用這些 API.
參考下面的程式碼:
import {Component, Inject, NgModule} from '@angular/core';
import {LOCAL_STORAGE} from '@ng-web-apis/common';
@Component({...})
export class SomeComponent {
constructor(@Inject(LOCAL_STORAGE) localStorage: Storage) {
localStorage.getItem('key');
}
}
上面的示例使用來自 @ng-web-apis/common 包的 LOCAL_STORAGE 令牌。 但是當我們在伺服器上執行這段程式碼時,我們會得到一個錯誤。 只需從 AppServerModule 的 providers 中新增來自 @ng-web-apis/universal 包的 UNIVERSAL_LOCAL_STORAGE,並透過令牌 LOCAL_STORAGE,這樣就能獲得伺服器的 localStorage 實現。
import { NgModule } from '@angular/core';
import {
ServerModule,
} from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { UNIVERSAL_LOCAL_STORAGE } from '@ng-web-apis/universal';
@NgModule({
imports: [
AppModule,
ServerModule,
],
providers: [UNIVERSAL_LOCAL_STORAGE],
bootstrap: [AppComponent],
})
export class AppServerModule {}
看下面這段條件渲染程式碼:
<>
@Component({
selector: 'ram-root',
template: '<some-сomp *ngIf="isServer"></some-сomp>',
styleUrls: ['./app.component.less'],
})
export class AppComponent {
isServer = isPlatformServer(this.platformId);
constructor(@Inject(PLATFORM_ID) private platformId: Object){}
}
這個 Angular Component 需要獲取 PLATFORM_ID、目標平臺,並瞭解類的公共屬性。此屬性將在模板中與 ngIf 指令一起使用。
我們有一種更加優雅的實現:
首先建立一個 injection token:
<>
export const IS_SERVER_PLATFORM = new InjectionToken<boolean>('Is server?', {
factory() {
return isPlatformServer(inject(PLATFORM_ID));
},
});
然後建立一個自定義指令:
@Directive({
selector: '[ifIsServer]',
})
export class IfIsServerDirective {
constructor(
@Inject(IS_SERVER_PLATFORM) isServer: boolean,
templateRef: TemplateRef<any>,
viewContainer: ViewContainerRef
) {
if (isServer) {
viewContainer.createEmbeddedView(templateRef);
}
}
}
然後直接在 Component 上使用這個 structure Directive 就可以了:
<>
@Component({
selector: 'ram-root',
template: '<some-сomp *ifIsServer"></some-сomp>',
styleUrls: ['./app.component.less'],
})
export class AppComponent {}
額外的屬性已從元件中移除。Component 模板現在更簡單了,只用專注於它要實現的業務邏輯。