60

I like the idea of resolvers.

You can say that:

  • for a given route you expect some data to be loaded first
  • you can just have a really simple component with no observable (as retrieving data from this.route.snapshot.data)

So resolvers make a lot of sense.

BUT:

  • You're not changing the URL and displaying your requested component until you receive the actual response. So you can't (simply) show the user that something is happening by rendering your component and showing as much as you can (just like it's advised to, for the shell app with PWA). Which means that when having a bad connection, your user may have to just wait without visual indication of what's happening for a long time
  • If you are using a resolver on a route with param, let's take as an example users/1, it'll work fine the first time. But if you go to users/2, well nothing will happen unless you start using another observable: this.route.data.subscribe()

So it feels like resolvers might be helpful retrieving some data BUT in practice I wouldn't use them in case there's a slow network and especially for routes with params.

Am I missing something here? Is there a way to use them with those real constraints?

4

7 回答 7

18

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

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

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

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

方案二:获取解析器中的有效文件名列表,检查URL中的文件名是否有效,然后获取文件数据。

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

重要:: 如果您想在用户被路由到 URL 之前获取数据,请使用解析器。解析器可以包括服务调用,这将为我们带来加载下一页所需的数据。

于 2018-09-06T21:57:36.653 回答
8

这就是我使用解析器的原因:

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

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

于 2018-08-26T22:39:50.053 回答
6

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

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

例如,如果您使用的是 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))
    );
}

如果您正在使用ngrx,则可以通过在解析进行时调度操作来执行类似的操作。

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

于 2018-07-03T00:02:48.997 回答
6

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

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

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

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

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

于 2020-06-10T21:13:33.697 回答
5

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

于 2019-07-11T20:24:51.133 回答
3

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

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

  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 稍后仍需要加载一些数据,您也可以在这种情况下使用加载指示器。

于 2021-08-16T15:09:38.257 回答
0

从我的角度来看,如果您不介意在加载数据时无法立即将用户导航到新部分,则可以在组件代码中使用它们来简化和清晰。

为了改善用户体验,让用户知道正在加载某些内容,您可以创建一个服务来保持加载状态。它将订阅路由器事件并根据发生的情况将变量设置为不同的值。例如,我已经习惯了 3 种基本状态:initial_load、第一次运行应用程序时、resolving、正在解析和done.

每当启动导航或第一次加载应用程序时,包含<router-outlet>标签的组件将显示一个加载器,直到加载状态设置为完成。

对于第二个问题,关于从 users/1 到 users/2 的导航,你是完全正确的。要解决这个问题,您必须订阅用户详细信息组件中的路由参数更改。这通常不会发生在我使用过的应用程序中,这些应用程序有一个表格,您可以在其中选择一个用户,而要选择另一个您必须导航回列表。

于 2021-02-23T12:01:53.387 回答