Angular 尽管 `runGuardsAndResolvers` 设置为 'always',但解析器未更新或重新获取数据?

Angular resolver not updating or refetching data, despite `runGuardsAndResolvers` set to 'always'?

我有一组 Angular 路由,其中​​包含一个实体列表,以及两个用于创建此类实体和编辑现有实体的子路由。实体列表附加了一个 resolver 以在显示之前预取组件的数据。这些路由可以总结如下,代码中是如何描述这些路由的,请往下看。

但是,如果我在 /items/create,并成功创建项目,导航 "back" 到 /items 或任何编辑路线,甚至返回 / 将不会导致我的解析器像我预期的那样获取更新的数据。 尽管 runGuardsAndResolvers 属性 设置为 "always" 是这个 属性 应该启用我正在寻找的功能。

为什么会这样,我如何才能启用我正在寻找的功能,而不 做一些事情,比如在我的组件中订阅路由器事件和复制逻辑。

路线

const itemRoutes: Routes = [
    {
        path: '', // nb: this is a lazily loaded module that is itself a child of a parent, the _actual_ path for this is `/items`. 
        runGuardsAndResolvers: 'always',
        component: ItemsComponent,
        data: {
            breadcrumb: 'Items'
        },
        resolve: {
            items: ItemsResolver
        },
        children: [
            {
                path: 'create',
                component: CreateItemComponent,
                data: {
                    breadcrumb: 'Create Item'
                }
            },
            {
                path: ':itemId/edit',
                component: EditOrArchiveItemComponent,
                data: {
                    breadcrumb: 'Editing Item :item.name'
                },
                resolve: {
                    item: ItemResolver
                }
            }
        ]
    }
];

物品解析器

@Injectable()
export class ItemsResolver implements Resolve<ItemInIndex[]> {
    public constructor(public itemsHttpService: ItemsHttpService, private router: Router) {}

    public resolve(ars: ActivatedRouteSnapshot, rss: RouterStateSnapshot): Observable<ItemInIndex[]> {
        return this.itemsHttpService.index().take(1).map(items => {
            if (items) {
                return items;
            } else {
                this.router.navigate(['/']);
                return null;
            }
        });
    }
}

ItemsHttpService

(应要求张贴)

@Injectable()
export class ItemsHttpService  {

    public constructor(private apollo: Apollo) {}

    public index(): Observable<ItemInIndex[]> {
        const itemsQuery = gql`
            query ItemsQuery {
                items {
                    id,
                    name
                }
            }
            `;

        return this.apollo.query<any>({
            query: itemsQuery
        }).pipe(
            map(result => result.data.items)
        );
    }
}

首先.

不是你应该如何使用Resolver

仅当存在所需数据对您的路线至关重要时,才应使用 Resolver。因此,当您有一条专用数据的路由并且您希望在继续加载组件之前确保该数据存在时,解析器就有意义了。

我们以你的Item App为例,站在用户的角度来面对问题。

/项目

用户导航到 /items,他首先看到的是什么。这是因为解析器在用户到达组件之前获取所有项目。只有在解析器从 api 获取所有项目后,用户才会被委托给 ItemsComponent

但这不是必要的,而且用户体验不好。

为什么不直接将用户委托给 ItemsComponent。例如,如果你有一个很棒的 table,有很多按钮和很酷的 css,你可以让你的应用有时间渲染这些元素,而你的 Service 从 api。为了向用户表明数据正在加载,您可以在 table 中显示一个漂亮的 progress-spinner(-bar) 直到数据到达。

/items/create

这里也一样。用户必须等到 Resolver 从你的 api 获取所有数据,然后他才能到达 CreateComponent。应该清楚的是,仅用户可以创建一个项目来获取所有项目是一种矫枉过正。所以不需要 Resolver

/项目/:itemId/edit

这是解析器的最佳位置。当且仅当该项目存在时,用户应该能够路由到该组件。但是,当您定义一个 Resolver 来在您的一个子路由被激活时获取所有项目时,用户将面临与以前的路由相同的糟糕用户体验。

解决方案

从您的路线中删除 ItemsResolver。

const routes: Routes = [
  {
      path: '',
      component: ItemsComponent,
      children: [
        {
          path: '',
          component: ItemsListComponent,
          data: {
            breadcrumb: 'Items'
          },
        },
        {
          path: 'create',
          component: CreateItemComponent,
          data: {
            breadcrumb: 'Create Item'
          },
        },
        {
          path: ':itemId/edit',
          component: EditOrArchiveItemComponent,
          data: {
            breadcrumb: 'Editing Item :item.name'
          },
          resolve: {
            item: ItemResolverService
          }
        }
      ]
  }
];

您可以在 ItemsService 中定义一个 BehaviorSubject,它像缓存一样保存您最后获取的项目。每当用户从 createedit 路由回 ItemsComponent 时,您的应用程序可以立即呈现缓存的项目,同时您的服务可以在后台同步项目。

我实现了一个小的工作示例应用程序: https://stackblitz.com/github/SplitterAlex/Whosebug-48640721

结论

A Resolver 仅在编辑项路径中有意义。

谨慎使用Resolver

如果您导航到同一个路由条目,angular 正在重复使用该路由,因此解析器不是 re-executed。要更改此行为,您可以从 routeReuseStrategy 覆盖 shouldReuseRoute。

public ngOnInit(): void {

     this.router.routeReuseStrategy.shouldReuseRoute = () => false;

}