为什么要使用Angular的解析器?

max*_*992 37 angular angular-resolver

我喜欢resolvers 的想法.

您可以这样说:
- 对于给定的路径,您希望首先加载一些数据
- 您可以拥有一个没有可观察的非常简单的组件(从中检索数据this.route.snapshot.data)

解析器很有意义.

但是:
- 在收到实际响应之前,您不会更改URL并显示您请求的组件.因此,您不能(简单地)通过呈现组件并尽可能多地显示用户来显示用户正在发生的事情(就像建议使用PWA的shell应用程序一样).这意味着当连接不良时,您的用户可能只需要等待很长时间没有视觉指示正在发生的事情
- 如果您在使用参数的路线上使用解析器,让我们以其为例users/1,它将正常工作第一次.但是如果你去users/2,除非你开始使用另一个observable,否则什么都不会发生:this.route.data.subscribe()

所以感觉解析器可能有助于检索一些数据但在实践中我不会使用它们以防万一网络速度慢,特别是对于带有参数的路由.

我在这里错过了什么吗?有没有办法将它们与那些真正的约束一起使用?

Tim*_*thy 10

实际上,我目前正在重构一个使用大量解析器的应用程序,对它们进行了很多思考,并认为它们最大的问题是它们会改变数据,您必须映射从activatedRoute获取的数据。换句话说,它增加了维护应用程序的复杂性和问题,而直接服务注入则没有这个问题......此外,由于解析器是同步的,在大多数情况下,这确实会降低用户体验......


小智 8

解析器:它甚至在用户被路由到新页面之前就被执行。

每当需要在组件初始化之前获取数据时,执行此操作的正确方法是使用解析器。解析器同步运行,即解析器将等待异步调用完成,并且只有在处理了异步调用之后,它将路由到相应的URL。因此,组件初始化将等待直到回调完成。因此,如果您想做一些事情(服务呼叫),即使在组件初始化之前,您就来对地方了。

示例场景:我正在研究项目,其中用户将传递要在URL中加载的文件名。根据传递的名称,我们将在ngOnInit中进行异步调用并获取文件。但这是一个问题,如果用户在URL中传递了不正确的名称,我们的服务将尝试获取服务器上不存在的文件。在这种情况下,我们有两种选择:

选项1:在ngOnInit中获取有效文件名列表,然后调用实际服务以获取文件(如果文件名有效)。这两个调用都应该是同步的

选项2:在解析器中获取有效文件名列表,检查URL中的文件名是否有效,然后获取文件数据。

选项2是更好的选择,因为解析器处理呼叫的同步性。

重要说明::甚至在将用户路由到URL之前,要获取数据时,请使用解析器。解析程序可能包含服务调用,这将为我们带来加载下一页所需的数据。


alb*_*anx 7

这就是为什么我会使用解析器:

  • 单一职责,我的组件需要一些数据,在某些情况下,这些数据由缓存或状态提供,当丢失时我需要先获取它们,然后直接传递,而无需更改我的组件。示例:带有列表的搜索结果页面myapp.com/lists,导航到列表的一个元素myapp.com/lists/1,显示该元素的详细信息,无需获取数据,已由搜索完成。然后假设您直接导航到myapp.com/lists/1您需要获取的组件,然后导航到该组件
  • 将您的组件与路由器逻辑隔离
  • 我的组件不应该管理获取请求的加载状态,它的唯一职责是显示数据

将解析器视为您的应用程序和组件之间的中间件,您可以在包含的父组件中管理加载视图<router-outlet>


Pau*_*ole 7

Angular 路由数据解析器挂钩到路由器的导航事件链,有助于提供从父路由(包含)到所有子路由所需的数据。

从官方 Angular Router 文档来看,这是路由器事件发生的顺序:

  1. NavigationStart:导航开始。
  2. RouteConfigLoadStart:在路由器延迟加载路由配置之前。
  3. RouteConfigLoadEnd:路线延迟加载后。
  4. RoutesRecognized:当路由器解析URL并识别路由时。
  5. GuardsCheckStart:当路由器开始路由的保护阶段时。
  6. ChildActivationStart:当路由器开始激活路由的子级时。
  7. ActivationStart:当路由器开始激活路由时。
  8. GuardsCheckEnd:当路由器成功完成路由的保护阶段时。
  9. ResolveStart:当路由器开始路由的解析阶段时。
  10. ResolveEnd:当路由器成功完成路由的解析阶段时。
  11. ChildActivationEnd:当路由器完成激活路由的子级时。
  12. ActivationEnd:当路由器完成路由激活时。
  13. NavigationEnd:导航成功结束时。
  14. NavigationCancel:取消导航时。
  15. NavigationError:由于意外错误导致导航失败时。
  16. Scroll:当用户滚动时。

那么为什么需要解析器呢?

单一职责原则 (SRP) 和 DRY(不要重复自己)。数据获取(和/或缓存)通常在服务中实现,而解析器选择从哪个服务提供哪些数据,即使解析器是同步的,如果数据被缓存,用户也不会注意到落后。

例子

数据可以在路由处获取(解析)一次(并且可能缓存),/items并作为所有子路由的活动路由数据快照的一部分提供,例如/items/:id/items/:id/edit

在此示例中,项目列表被提取一次,当用户只需要编辑或查看一个项目时,不需要再次提取该项目,因为它已经在作为父路由/items解析器的一部分提取的列表中可用。

其他问题

为了解决每个人的担忧,即您无法向用户显示应用程序正在等待某些数据加载,然后再导航到新页面,解决方案很简单:只需挂接到路由器的事件(例如在您的应用程序组件中),然后监听事件ResolveStartResolveEnd显示或隐藏加载动画或加载覆盖组件或任何您需要执行的操作来让用户知道数据正在加载。例如,对于移动应用程序,所使用的图案通常是中心有旋转器的叠加层。

编辑:自从写这篇文章以来,我得出的结论是路由解析器是反模式。通过立即导航到用户单击的路线并更快而不是延迟后显示某些内容,您可以获得更好的用户体验,即使 UI 稍后仍需要加载一些数据,在这种情况下您可以使用加载指示器。


Joh*_*nes 6

解析器在路由器导航开始附近为您提供一个钩子,它可以让您控制导航尝试。这给了你很大的自由,但不是很多硬性规定。

您不必在组件中使用解析的结果。您可以将解析用作挂钩。由于您提到的原因,这是我首选的使用方式。在您的组件中使用结果要简单得多,但它具有同步权衡。

例如,如果您正在使用 Material 或 Cdk 之类的东西,您可以调度一个“加载”对话框,以在解析开始时显示进度指示器,然后在解析结束时关闭对话框。像这样:

constructor(route: ActivatedRouteSnapshot, myService: MyService, dialog: MatDialog) {}

resolve() {
    const dialogRef = this.dialog.open(ProgressComponent);
    return this.myService.getMyImportantData().pipe(
        tap(data => this.myService.storeData(data)),
        tap(() => dialogRef.close()),
        map(() => true),
        catchError(err => of(false))
    );
}
Run Code Online (Sandbox Code Playgroud)

如果您正在使用ngrx,您可以通过在解析过程中分派操作来做类似的事情。

当您以这种方式使用 Resolve 防护时,没有特别强烈的理由使用 Resolve 防护而不是 CanActivate 防护。这种选择归结为语义。我发现在 CanActivate 上进行门验证并从 Resolve 启动数据检索更明显。当这些主题被允许混合时,它就变成了一种随意的选择。


Gor*_*ant 6

我一直在想完全相同的事情。

我遇到的这个话题最直观的讨论在这里:https : //angular.schule/blog/2019-07-resolvers

作者基本上将其归结为:如果您已经使用了解析器并且没有任何用户体验问题,那就去做吧。但大多数时候,解析器会增加不必要的复杂性,您最好采用使用“智能容器”和“展示组件”结构的反应式方法。很少有例外。

使用这种结构,智能组件充当更动态的解析器形式,您的表示组件处理伪同步数据的显示。

据我所知,解析器本质上是那些不太习惯使用响应式模式的人的拐杖。