简体   繁体   English

Angular ngrx - 避免使用沙盒模式进行嵌套订阅

[英]Angular ngrx - Avoid nested subscribe with sandbox pattern

I need to understand if I'm approaching good to ngrx and sandbox pattern...This is my doubt: I have a sandbox file with some functions, example:我需要了解我是否正在接近 ngrx 和沙盒模式......这是我的疑问:我有一个带有一些功能的沙盒文件,例如:

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

In this function I need to call a service and manage the success with an action dispatch.在这个 function 中,我需要调用服务并通过操作调度来管理成功。

In my container I need to use two selectors for finding userId (userId$) and companyId (company$): the parameters that I need for calling the sandbox function;在我的容器中,我需要使用两个选择器来查找 userId (userId$) 和 companyId (company$):调用沙箱 function 所需的参数; so in the ngOnInit of my container-component I do this:所以在我的容器组件的 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();

Is this a correct way or are these nested subscriptions?这是正确的方式还是这些嵌套订阅? When i call the sandbox function I do the subscription inside the function declaration...I can't find a real solution for avoid this...I need to subscribe to the selectors for finding the parameters that I need and then invoke the sandbox function.当我调用沙箱 function 时,我在 function 声明中进行订阅...我找不到避免这种情况的真正解决方案...我需要订阅选择器以查找我需要的参数然后调用沙箱function。 Which is the correct approach?哪个是正确的方法?

You just moved the nested subscription to a separate method.您刚刚将嵌套订阅移至单独的方法。 What if you have one more?如果你还有一个呢? You don't really solve the problem but just move it.你并没有真正解决问题,只是移动它。

In the example you gave, you can use a flattening operator in a combination with tap .在您给出的示例中,您可以将展平运算符与tap结合使用。

In your case:在你的情况下:

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();

Then you would have to manage only 1 subscription.那么您将只需要管理 1 个订阅。 You can read up more about the flattening operators ( switchMap , mergeMap , concatMap , etc.) here .您可以在此处阅读有关展平运算符( switchMapmergeMapconcatMap等)的更多信息

PS. PS。 As suggested by @gunnar-b you should look into effects and how to use them since it looks like you are not fully utilizng ngrx.正如@gunnar-b 所建议的那样,您应该研究效果以及如何使用它们,因为看起来您并没有完全利用 ngrx。

I would say it's almost never best practice to manage a subscription in your code in an NGRX app.我想说在 NGRX 应用程序中管理代码中的subscription几乎从来都不是最佳实践。 The subscription can cause a memory leak if your code fails to clean it up correctly when it goes out of scope (for example, when the component that created it gets destroyed).如果您的代码在超出 scope 时(例如,当创建它的组件被破坏时)未能正确清理它, subscription可能会导致 memory 泄漏。

In a typical app, Observables should be consumed safely in one of two ways:在一个典型的应用程序中,应该通过以下两种方式之一安全地使用 Observables:

  1. Components can use the async pipe to render the output from an Observable组件可以使用async pipe 从 Observable 渲染 output
  2. @ngrx/effects can be used to handle side effects resulting from an action @ngrx/effects可用于处理动作产生的副作用

#1 The async pipe #1 async pipe

A component can create an Observable field and set it up on onInit:组件可以创建一个 Observable 字段并在 onInit 上设置它:

public titleUppercase$: Observable<string>

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

In this setup, Angular manages the subscription for you automatically, so there is no risk of a memory leak.在此设置中,Angular 会自动为您管理订阅,因此不存在 memory 泄漏的风险。

If the pipe logic is complex (for example, using multiple observables with combineLatest ), it's a good idea to extract this logic to a Service class for testability.如果 pipe 逻辑很复杂(例如,将多个可观察对象与combineLatest一起使用),最好将此逻辑提取到服务 class 以进行可测试性。

#2 @ngrx/effects #2 @ngrx/effects

Effects are for managing "side effects"... ie, asynchronous processes that do something other than make changes to the state of your Store.效果用于管理“副作用”......即,异步进程除了对您的商店的 state 进行更改之外,还执行其他操作。 An HttpClient call to get data from the server is an example of a side effect.从服务器获取数据的HttpClient调用是副作用的一个示例。

Check out the user guide for the @ngrx/effects library here:在此处查看@ngrx/effects库的用户指南:

https://ngrx.io/guide/effects https://ngrx.io/guide/effects

Here is an example of an effect which loads data from the server in response to a user's action:下面是一个从服务器加载数据以响应用户操作的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())
    ))
  ));

With this approach, your app components can simply observe state to render themselves and dispatch actions when the user does something.使用这种方法,您的应用程序组件可以简单地观察 state 来呈现自己并在用户执行操作时调度操作。 All other complex business logic that happens in the background can be handled using the reducer functions and effects.所有其他在后台发生的复杂业务逻辑都可以使用 reducer 函数和效果来处理。 This makes your UI application very easy to unit test.这使您的 UI 应用程序非常容易进行单元测试。

If all of your effects have very similar needs and you want to use a sandbox pattern, you might introduce a common injectable dependency or base class that can be leveraged by all of your effects.如果您的所有效果都具有非常相似的需求并且您想使用沙盒模式,您可能会引入一个通用的可注入依赖项或基础 class,您的所有效果都可以利用它。 You could also have a library of common actions that components can dispatch, or even an injectable service that provides sandbox functionality to components with methods for dispatching actions and setting up selector Observables.您还可以拥有一个组件可以分派的常用操作库,甚至是一个可注入服务,该服务为组件提供沙盒功能,并提供用于分派操作和设置选择器 Observables 的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM