布莱恩F爱
从俄勒冈州波特兰的一位专注yobet英雄联盟于Angular、Web技术和Node.js的谷歌开发专家那里学习。
广告 ·ultimatecourses.com
以正确的方式学习Angular的终极课程

角转移状态

学习如何有效地管理Angular传输状态和使用Universal静态渲染。

什么?

角的TransferState这个类使服务器端渲染(SSR)和预渲染(SSR) Angular应用能够使用从服务器获取的数据在浏览器中高效呈现。yobet外围让我们来分析一下,首先从潜在的问题开始。

的问题

如果你使用SSR或预渲染应用策略,那么过程大致是这样的:

  1. 服务器上的预渲染或呈现应用程序
  2. 浏览器获取呈现的HTML和CSS,并显示“静态”应用程序
  3. 浏览器获取、解析、解释和执行JavaScript
  4. Angular应用被引导,用新的“运行”应用替换了整个DOM树
  5. 应用程序初始化,通常从远程服务器或API获取数据
  6. 用户与应用程序进行交互

在这种情况下有两个问题:

  1. DOM水化目前正在替换整个节点树并重新绘制应用程序
  2. 由于SSR或预渲染站点策略,应用程序正在获取理论上已经拥有并已显示给用户的数据

第一个问题是Angular目前还没有解决的问题,坦率地说,这是一个复杂的挑战。然而,第二个问题目前在Angular(到撰写本文时的版本9)中使用TransferState

是什么TransferState吗?

转移状态是我们的策略:

  1. 使用SSR或预呈现策略获取呈现完整“静态”应用程序所需的数据
  2. 序列化数据,并将数据与初始文档(HTML)响应一起发送
  3. 在应用程序初始化时,在运行时解析序列化数据,避免重复获取数据

技术上,数据是通过JSON.stringify ()和解析通过JSON.parse ()。但是,通常我们不需要担心这个,因为这是由TransferState服务在角。

让我们快速回顾一下文档:

存储中的值使用JSON.stringify/JSON.parse进行序列化/反序列化。因此,只有布尔、数字、字符串、null和非类对象将以非有损的方式序列化和反序列化。

如果您的应用程序需要将不符合这些要求的状态从服务器传输到客户机,那么您将需要实现一种将数据转换为我称之为“兼容”数据的策略。

我的特定用例是序列化来自Firestore的包含DocumentReference类。文档引用是Firestore中的一个外键,它是Firebase SDK提供的一个专用类。

目标

我使用转移状态的目标是:

  1. 利用RxJS数据流“拦截”获取的数据使用SSR或预渲染
  2. 实现一种策略,将不符合标准的数据转换为符合标准的数据进行序列化,并依次将序列化的符合数据转换回原始结构

第三个目标对于我的用例是必要的,因为我需要转换Firestore的DocumentReference类转换成可以被TransferState

开始

首先TransferState我们需要的服务:

  1. 导入ServerTransferStateModule在我们的服务器应用程序模块中。
  2. 导入BrowserTransferStateModule在我们的浏览器应用程序模块。

当使用服务器端呈现时,您通常会有一个单独的应用程序入口点,最常见的形式是src / main.server.ts文件。而且,浏览器入口点最常见的是src / main.tsAngular CLI生成的文件。然后这些文件将通过NgModule类。

让我们先打开src / app / app.server.module.ts并导入ServerTransferStateModule:

@NgModule({进口:(AppModule,ServerModule,ServerTransferStateModule],引导:(AppComponent],})出口AppServerModule{}

然后,让我们导入BrowserTransferStateModulesrc / app / app.module.ts文件:

@NgModule({声明:(AppComponent],进口:(BrowserModulewithServerTransition({appId:“serverApp”}),BrowserTransferStateModule,CoreModule,/ /代码省略],引导:(AppComponent],})出口AppModule{}

TransferStateService

为了解决在RxJS流中截取数据的第一个目标,我创建了一个TransferStateService可以提供给Angular获取数据的组件类。

@可注射的({providedIn:“根”,})出口TransferStateService{/** *状态键。* /私人=地图<字符串,StateKey<字符串>>();构造函数(@注入(PLATFORM_ID)私人只读的platformId,私人只读的transferState:TransferState){}获取<T>(关键:字符串,observableInput:可观测的<T>,defaultValue吗?:T,转换器吗?:TransferStateConverter<T>):可观测的<T>{如果((关键)){返回(得到(关键,defaultValue,转换器))(利用(()= >删除(关键)));}返回observableInput(利用((价值)= >(关键,价值,转换器)));}得到<T>(关键:字符串,defaultValue吗?:T|,转换器吗?:TransferStateConverter<T>):T|{如果(!(关键)){返回defaultValue||;}常量价值=transferState得到<T>(getStateKey(关键),defaultValue);返回转换器吗?转换器fromTransferState(价值):价值;}(关键:字符串):布尔{返回transferStatehasKey(getStateKey(关键));}删除(关键:字符串):无效{如果(!(关键)){返回;}transferState删除(getStateKey(关键));}<T>(关键:字符串,价值:T,转换器吗?:TransferStateConverter<T>):无效{如果(isPlatformServer(platformId)){如果((关键)){控制台警告(使用键将现有值设置为TransferState: '$ {关键});}如果(!环境生产){控制台日志(为:'存储TransferState$ {关键});}transferState(getStateKey(关键),转换器吗?转换器toTransferState(价值):价值);}}私人getStateKey(关键:字符串):StateKey<字符串>{如果((关键)){返回得到(关键);}(关键,makeStateKey(关键));返回得到(关键);}}

让我们快速回顾:

  • 首先,我们注入platformId字符串和TransferState类实例。我们将使用platformId来检查我们是否在服务器的上下文中执行了Angular应用。如果使用基于浏览器的预呈现策略,则需要调整此策略,以确定应用程序何时在预呈现上下文中执行。
  • 然后我们定义了a获取方法,接受关键唯一标识数据的observableInput,并可选defaultValue转换器。下面我们将更多地讨论转换器策略。
  • 获取“拦截”方法下一个通知,并使用TransferState服务。的类中的方法处理此问题。
  • 得到方法返回TransferState如果存在,则返回defaultValue。它还处理将数据从“符合”的序列化值转换回原始值。
  • 方法返回一个布尔值,该值指示数据是否存在TransferState类。
  • 删除方法将数据从TransferState类。这是为了确保后续下一个客户机上的RxJS可观察流中的通知不是从服务器端获取的过时数据。
  • 方法将数据存储到TransferState服务,以及将不兼容的数据转换为可序列化的值。
  • 最后,私人getStateKey方法返回StateKey关键字符串提供。

控件的示例实现如下TransferStateService在Angular解析器中:

出口RuleResolver实现了解决<规则>{构造函数(私人只读的rulesService:RulesService,私人只读的transferStateService:TransferStateService){}解决(快照:ActivatedRouteSnapshot){返回transferStateService获取<规则>(规则,rulesServicegetBySlug(快照paramMap得到(“鼻涕虫”)));}}

上面我们用的是获取注入方法TransferStateService实例来存储在RuleService.getBySlug ()方法。

TransferStateConverter

正如我前面在我的特定用例中提到的,我有一些不符合要求的值,需要将它们转换为序列化TransferState服务。为了解决第二个目标,我想创建一个定义良好、类型安全的数据转换实现。

首先,让我们定义一个新的抽象TransferStateConverter类:

进口{可序列化的}“@lkt-core /类型”;出口摘要TransferStateConverter<T>{/** *由TransferStateService调用,将序列化的值转换为t类型的对象*/摘要fromTransferState(数据:可序列化的<T>):T;/** *由TransferStateService调用,将数据转换为可由TransferState序列化的值。* /摘要toTransferState(数据:T):可序列化的<T>;}

两个必需的方法必须由转换器实现:

  1. toTransferState方法接受不符合的数据并返回可序列化的价值。
  2. fromTransferState方法接受的可序列化的值,并返回不符合的值。

toTransferState方法将在服务器或预呈现上下文中执行。和,fromTransferState方法将在客户机上的浏览器上下文中执行。

您还需要定义一个新的可序列化的类型:

出口类型可序列化的<T>=布尔|数量|字符串||对象|T;

可序列化的类型表示可以序列化为JSON,以便与TransferState服务。

最后,让我们看一个示例实现。我们将继续序列化a规则。在我的应用程序中,这是从包含不兼容的Firestore返回的对象DocumentReference类。

下面是模型接口的一部分:

出口接口规则{/ /代码省略相关的吗?:数组<DocumentReference>;标签吗?:数组<DocumentReference>;}

请注意,相关的标签属性的数组DocumentReferenceFirebase SDK提供的类。

让我们创建一个新RuleConverter类,可以委托给它来转换从、到、可序列化的数据:

类型SerializedRule=规则&{相关的吗?:字符串(];标签吗?:字符串(];};@可注射的({providedIn:“根”,})出口RuleConverter扩展TransferStateConverter<规则>{构造函数(私人只读的rulesService:RulesService,私人只读的tagsService:TagsService){超级();}fromTransferState(规则:SerializedRule):规则{返回{规则,相关的:规则相关的吗?规则相关的地图((id:字符串)= >rulesServicegetRef(id)):(],标签:规则标签吗?规则标签地图((id:字符串)= >tagsServicegetRef(id)):(],};}toTransferState(规则:规则):可序列化的<规则>{返回{规则,相关的:规则相关的吗?规则相关的地图((裁判)= >裁判id):(],标签:规则标签吗?规则标签地图((裁判)= >裁判id):(],};}}

让我们跳过一些特定的实现细节,而是将重点放在TransferStateConverter抽象类:

  • 首先,我们扩展TransferStateConverter为转换器提供泛型类型的类。在这种情况下,这个转换器是用来转换a规则
  • 构造函数()函数为我的实现提供注入依赖项。
  • fromTransferState ()方法接受的规则参数是aSerializedRule并返回一个规则。在我的实现中,我转换了一个文档id,由DocumentReference到一个字符串使用getRef ()为我服务的方法。
  • toTransferState ()方法接受的规则是不服从的规则对象的DocumentReference类。方法将引用转换为id字符串值。

我们现在可以将转换器实现到解析器中:

常量规则=“规则”;出口RuleResolver实现了解决<规则>{构造函数(私人只读的ruleConverter:RulesConverter,私人只读的rulesService:RulesService,私人只读的transferStateService:TransferStateService){}解决(快照:ActivatedRouteSnapshot){返回transferStateService获取(规则,rulesServicegetBySlug(快照paramMap得到(“鼻涕虫”)),,ruleConverter);}}

我喜欢这个延迟模式的地方在于,我提供了RuleConverter类实例的TransferStateService有效地将值转换为可序列化值,或从可序列化值转换。

结论

我通过定义一个TransferStateService以及定义扩展抽象类的模式TransferStateConverter类。

布莱恩F爱

嗨,我是布莱恩。我对TypeScript, Angular和Node.js感兴趣。我和我最好的朋友邦妮结婚了,我住在波特兰,我经常滑雪。