Angular 服务器端渲染应用的一个错误消息 localStorage is not defined

举报
Jerry Wang 发表于 2022/11/27 09:58:47 2022/11/27
【摘要】 本文分享作者在 Angular 开发过程中遇到的一个开启了服务器端渲染模式后,应用程序执行时遇到的错误消息的分析思路和解决方案。 在 Angular 应用开发中,我们在 TypeScript 代码里调用 localStorage. 本文分享 Angular 开发人员遇到错误消息 ReferenceError: localStorage is undefined 之后的分析思路。

本文分享作者在 Angular 开发过程中遇到的一个开启了服务器端渲染模式后,应用程序执行时遇到的错误消息的分析思路和解决方案。

在 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 模板现在更简单了,只用专注于它要实现的业务逻辑。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。