如何允许同一组件的多个选项卡同时保持其状态?

问题描述 投票:0回答:2

我的应用程序使用由服务管理的延迟加载选项卡系统。 当用户在导航菜单上选择一个选项时,会发生两件事:

  • 在选项卡服务中,一个条目被添加到选项卡数组中。
  • 一条新路线被激活并加载了适当的组件。

这很好用,但我需要更复杂的东西:

  • 能够单击任何选项卡并恢复其状态 -> 如果表格已填写,按钮已被单击等。当我路由回我的组件时,我想要完全相同的状态。据我了解,我认为实施
    RouteReuseStrategy
    最适合这种行为。
  • 能够打开同一组件的多个实例。如果我的用户从导航菜单中选择相同的选项两次,我想打开两个选项卡。每个选项卡都有自己的状态。我希望能够在导航到这些选项卡中的任何一个时恢复该状态。

目前,我找到的唯一解决方案是将每个组件的状态存储在会话存储中,并在单击选项卡时更新组件状态。为此,我的组件需要订阅

NavigationStart
NavigationEnd
事件。

  • NavigationStart -> 在会话存储中保存当前状态。
  • NavigationEnd -> 从会话存储加载当前状态。

这适用于相当简单的组件,但适用于一些包含数十个按钮、表单等的非常复杂的组件。它很快就会成为管理的噩梦,因为我必须为每个组件编写加载和保存逻辑。会话存储的大小有限,这也是这种方法的限制。

非常感谢任何建议。

angular tabs lazy-loading angular-routing
2个回答
0
投票

您可以使用

ngOnInit
ngOnDestroy
生命周期挂钩从某些持久存储中加载/存储状态。您建议使用 local-storage,这是一个不错的选择,特别是如果您还希望在浏览器中重新加载/刷新或用户稍后重新访问页面时恢复状态。否则会话存储也是一个选项(阅读 MDN 上的差异这里)。如果不希望在页面重新加载时恢复,您也可以简单地将状态存储在某些服务中(存储在浏览器内存中而不是 Web 存储中)。

阅读更多关于 Angular 生命周期钩子的信息在官方 Angular 文档.


更新

正如您在评论中提到的,如果您路由到完全相同的组件而不破坏它,这将不起作用。

在这种情况下,您可以使用路由解析器:

假设您的应用程序或模块中有一个

DataService
实例,您要使用它来提供数据。

为您的

TabComponent
配置路线,例如:

{
  path: '',
  component: TabPageComponent,
  runGuardsAndResolvers: 'always', // Set flag to run always
  resolve: {
    // Provide the data service, assuming it is available in the injector
    data: DataService, 
  },
}

现在你的

DataService
应该实现
Resolve
'@angular/router'
接口:

import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';

export class DataService implements Resolve<any> {

  // constructor with dependencies should be implemented as needed for your case.

  public resolve(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<any> |
    // Do your thing here using route and state.
    // i.e. API call, load from Web Storage or simply from memory in this service.
  }

}

现在,每次您的路由更改时,路由器都会在您的服务中调用 resolve 。在您的组件中,您可以订阅激活的路由数据,这是一个相应发出的可观察对象:

import { ActivatedRoute } from '@angular/router';

//...

class TabComponent implements OnInit {

  constructor(private route: ActivatedRoute) {}

  public ngOnInit(): void {
    this.route.data.subscribe((data) => {
      const data = data.data;
      // Do something with your data.
    }
  }

}

替代方法是创建一个可观察对象并直接在您的视图中使用它:

import { ActivatedRoute } from '@angular/router';

//...

class TabComponent implements OnInit {

  constructor(private route: ActivatedRoute) {}

  public data$ = Observable<any>;

  public ngOnInit(): void {
    this.data$ = this.route.data.pipe(
      map(data => data.data),
    );
  }

}

根据您的需要,您可以通过使用其他选项之一配置它来更改

runGuardsAndResolvers
以降低运行频率。有关可能的选项,请参阅Angular 文档


0
投票

对于遇到相同问题的任何人,可以通过实施

RouteReuseStrategy
来实现粘性状态。你可以在这里找到一个例子.

我认为这种方法不适用于同一组件的多个实例,但它确实适用。你只需要稍微调整一下实现:

  • 在组件的构造函数中声明
    this.router.routeReuseStrategy.shouldReuseRoute = () => false;
    ,这样即使路由没有改变也会加载新组件。
  • 存储的路线保存在地图中。在上面的文章中,这张地图的关键是不同的路线。我将我的 tabId 连接到键,这样我就可以为同一路由/组件设置不同的条目,具体取决于我要访问的选项卡。下面的例子:

缓存路由重用策略

    @Injectable()
    export class CacheRouteReuseStrategy implements RouteReuseStrategy {

        constructor(private linkService : LinkService) {
        }

        shouldDetach(route: ActivatedRouteSnapshot): boolean {
            return true;
        }

        store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
            // If previous route and active route are the same, handle is null.
            // In that case, we don't want to update the store (we don't want to set a null object for the route and loose informations)
            if (handle != null && this.linkService.previousLink != null){
                this.linkService.storedRoutes.set(route.routeConfig?.path + this.linkService.previousLink.tabId, handle);
            }
        }

        shouldAttach(route: ActivatedRouteSnapshot): boolean {
            if (this.linkService.activeLink != null) {
                return !!route.routeConfig && !!this.linkService.storedRoutes.get(<string>route.routeConfig.path + this.linkService.activeLink.tabId);
            } else {
                return false;
            }
        }

        retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        // @ts-ignore
            return this.linkService.storedRoutes.get(route.routeConfig.path + this.linkService.activeLink.tabId);
        }

        shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
            return future.routeConfig === curr.routeConfig;
        }

    }

我有一个名为 LinkService 的服务来跟踪我要离开的路线 (previousLink) 和我试图访问的路线 (activeLink)。 previousLink 是我想在离开路线时添加到商店的对象,而 activeLink 是我想在访问路线时从商店获取的对象。

    @Injectable({
      providedIn: 'root'
    })
    export class LinkService {
      links: LinkModel[];
      activeLink: LinkModel;
      previousLink: LinkModel;
      storedRoutes = new Map<string, DetachedRouteHandle>();
    }
    
    export interface LinkModel {
      route: string,
      parameters: string | null,
      tabId: string,
      tabTitle: string
    }

这些信息由另一个名为 TabService 的服务更新。它的作用非常简单(创建、更改和删除选项卡逻辑),但如果有人需要,我可以稍后发布。

© www.soinside.com 2019 - 2024. All rights reserved.