【问题标题】:Angular ngrx - Avoid nested subscribe with sandbox patternAngular ngrx - 避免使用沙盒模式进行嵌套订阅
【发布时间】:2021-02-24 05:06:40
【问题描述】:

我需要了解我是否对 ngrx 和沙盒模式很满意...这是我的疑问: 我有一个带有一些功能的沙箱文件,例如:

getFiles(userId: number, companyId: number) {
  this.fileService.getFiles(userId, companyId).subscribe(res => this.store.dispatch(myaction({files: res})))
}

在这个函数中,我需要调用一个服务并通过一个动作调度来管理成功。

在我的容器中,我需要使用两个选择器来查找 userId (userId$) 和 companyId (company$):调用沙盒函数所需的参数;所以在我的容器组件的 ngOnInit 中,我这样做:

combineLatest([this.company$, this.userId$])
  .pipe(
    filter(res => res[0] !== null && res[1] !== null),
    take(1),
    map(res => {
      let companyId = res[0].id;
      let userId = res[1];
      this.sandbox.getfiles(companyId, userId);
    })
   
  )
  .subscribe();

这是正确的方式还是这些嵌套订阅?当我调用沙箱函数时,我在函数声明中进行了订阅...我找不到避免这种情况的真正解决方案...我需要订阅选择器以查找我需要的参数,然后调用沙箱功能。哪种方法正确?

【问题讨论】:

  • 我建议查看效果并为动作、效果、reducers 和选择器创建指定文件(与组件分开)。例如。一个效果应该监听一个动作然后调用getFiles,并在成功时分发带有结果(或失败)的动作

标签: javascript angular typescript ngrx


【解决方案1】:

您刚刚将嵌套订阅移至单独的方法。如果你还有一个呢?你并没有真正解决问题,只是移动它。

在您给出的示例中,您可以将展平运算符与tap 结合使用。

在你的情况下:

sandbox.service.ts

getFiles(userId: number, companyId: number) {
  return this.fileService.getFiles(userId, companyId).pipe(
    tap(res => this.store.dispatch(myaction({files: res}))
  )
}

some.container.ts

combineLatest([this.company$, this.userId$])
  .pipe(
    filter(res => res[0] !== null && res[1] !== null),
    take(1),
    switchMap(res => {
      let companyId = res[0].id;
      let userId = res[1];
      return this.sandbox.getFiles(userId, companyId);
    })
   
  )
  .subscribe();

那么您将只需要管理 1 个订阅。您可以阅读有关扁平化运算符的更多信息(switchMapmergeMapconcatMap 等)here

PS。正如@gunnar-b 所建议的那样,您应该研究效果以及如何使用它们,因为看起来您并没有完全利用 ngrx。

【讨论】:

  • 谢谢。但是通过这种方式,我不能使用沙盒模式......在沙盒中,我将管理服务调用和操作。我的例子不正确?
【解决方案2】:

我想说,在 NGRX 应用程序的代码中管理 subscription 几乎从来都不是最佳实践。 subscription 可能会导致内存泄漏,如果您的代码在超出范围时无法正确清理它(例如,当创建它的组件被破坏时)。

在典型的应用中,应通过以下两种方式之一安全地使用 Observable:

  1. 组件可以使用 async 管道来渲染 Observable 的输出
  2. @ngrx/effects 可用于处理动作产生的副作用

#1 async 管道

组件可以创建一个 Observable 字段并在 onInit 上设置它:

public titleUppercase$: Observable<string>

ngOnInit(): void {
    this.titleUppercase$ = this.store.pipe(select(titleSelector)).map(title => title.toUpperCase());
}
<p>{{titleUppercase$ | async}}</p>

在此设置中,Angular 会自动为您管理订阅,因此不存在内存泄漏的风险。

如果管道逻辑很复杂(例如,使用combineLatest 的多个可观察对象),最好将此逻辑提取到服务类以进行可测试性。

#2 @ngrx/effects

Effects 用于管理“副作用”...即异步进程,它执行的操作不是更改 Store 的状态。从服务器获取数据的HttpClient 调用就是一个副作用示例。

在此处查看@ngrx/effects 库的用户指南:

https://ngrx.io/guide/effects

这是一个 effect 的示例,它从服务器加载数据以响应用户的操作:

  loadServerData$ = createEffect(() => this.actions$.pipe(
    // I dispatch an action with this type in the onInit method of my view component
    ofType('[My Data Page] User loaded the page'),
    // each time we see this page load action, trigger a service call to get the HTTP data
    switchMap(() => this.myApiService.getData$().pipe(
      map(data => {
        //the response is mapped to an action called myDataLoaded
        //myDataLoaded includes the response payload as a property
        return myDataLoaded({payload: data});
      }),
      //this catchError within the switchMap is necessary to prevent the effect from
      //halting if there is an error from the HTTP call
      catchError(err => {
        // TODO: notify users when there's an error
        console.error('Error loading the page data', err);
        return NEVER;
      }),
      //using startsWith inside the switchMap, I can trigger a loading action
      //the loading action can be used to display a loading indicator on the page
      startWith(myDataLoading())
    ))
  ));

使用这种方法,您的应用组件可以简单地观察状态以呈现自己并在用户执行某项操作时调度操作。所有其他在后台发生的复杂业务逻辑都可以使用 reducer 函数和效果来处理。这使您的 UI 应用程序非常容易进行单元测试。

如果您的所有效果都有非常相似的需求并且您想使用沙盒模式,您可能会引入一个通用的可注入依赖项或基类,您的所有效果都可以利用这些依赖项或基类。您还可以拥有一个组件可以分派的常用操作库,甚至是一个可注入服务,它为组件提供沙盒功能,并提供用于分派操作和设置选择器 Observables 的方法。

【讨论】:

    猜你喜欢
    • 2022-08-25
    • 2020-11-30
    • 2022-06-22
    • 2023-01-30
    • 1970-01-01
    • 2018-11-27
    • 2020-08-18
    • 2020-05-31
    • 2013-06-30
    相关资源
    最近更新 更多