在Angular应用程序中向Rxjs动态添加运算符 加载程序服务: MyFruitService MyAppleFruitComponent 更新1:

问题描述

在Angular应用程序中,我们有一个加载器组件,该组件通过输入setter属性接收可观察对象。

在设置器内部,我们首先将一个布尔isLoading设置为true,该模板内部开始显示加载微调器。 然后我们订阅可观察对象,然后在接收到数据时,将isLoading布尔值再次设置为false,使微调框消失:

// loading-component template:

<ng-container *ngIf="data; else errorOrLoading">
  ...
</ng-container>

<ng-template #errorOrLoading>
 ...
</ng-template>

// loading-component class (short version without unsubscribe):

  @input() set data$(data$: Observable<any>) {
    if (data$) {
      this.data = null;
      data$
        .subscribe(data => {
          this.data = data;
        });
    }
  }

如果我们只有来自Observable的一个事件,那么这很好用。 但是,当Observable发出多个事件时,由于不会调用setter,因此isLoading不会再次设置为true。

有没有一种方法可以动态添加一个额外的tap运算符,从而允许在给定的Observable链开始发出新事件之前设置this.data = null?

例如,如果Observable是:

myService.onChanges$.pipe(
  switchMap(() => this.getBackendData())
);

我们可以动态添加一个tap操作符来将管道更改为:

myService.onChanges$.pipe(
  tap(() => this.data = null),switchMap(_ => this.getBackendData())
);

更新:我选择简化加载程序控制,并将所有可观察到的相关逻辑移至服务,这感觉方式更具可伸缩性和灵活性。

解决方法

更新#1

解决方案#1。使用shareReplay()

根据@Reactgular answer

您所要做的就是使用shareReplay()运算符:

class MyService {
    public data$: Observable<any>;
    public loaded$: Observable<boolean>;

    constructor(private dataService: DataService) {
        this.data$ = this.dataService.loadData().pipe(
            startWith(null),// data$ emit a null before data is ready followed by the API data.
            shareReplay(1);
        );
        this.loaded$ = this.data$.pipe(
           mapTo(true),startWith(false)
        );
    }
}

您必须调用myService.data $ .subscribe()来触发流的首次读取以使数据准备就绪。您可以在构造函数中执行此操作,但请记住,Angular在首次使用之前不会创建服务。如果您希望数据被紧急加载,请在路由中使用解析器,或将服务注入NgModule构造函数中并在其中进行订阅。


解决方案#2。使用专用服务

更好的解决方案是引入一个LoaderService来处理组件/视图数据的加载。

取决于您的项目需求,它可以是singletonshared

让我们假设我们的服务将仅处理当前视图(共享服务)的加载状态

加载程序服务:

export class LoaderService {
  private  readonly  _loading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false)

  // returns value of `_loading` on the moment of accessing `isLoading`
  get isLoading(): boolean {
    return this._loading.getValue();
  }
  
  // returns value of `_loading` as Observable
  get isLoading$(): Observable<boolean> {
    return this._loading.asObservable();
  }
  
  // pushes `true` as a new value of `_loading` subject and notifies subscribers
  start(): void {
    this._loading.next(true);
  }
  
  // pushes `true` as a new value of `_loading` subject and notifies subscribers
  stop(): void {
    this._loading.next(false);
  }
  
}

假设我们有两项服务:

  • API-仅包含返回纯(未经修改的)http流的方法的声明
  • ComponentService-在将数据传递给表示组件之前准备数据的服务

MyFruitService


  constructor(
   ...,private api: MyFruitAPIService,private loader: LoaderService
  ) { ... }

  getApples(): Observable<T> {
    this.loader.start();
    
    return this.api.getApples()
     .pipe(
      finalize(() => this.loader.stop()) // finalize - will call a function when observable completes or errors
     );
  }
   

MyAppleFruitComponent


 readonly loading$ = this.loader.isLoading$.pipe(tap((loading) => {
   if (loading) {
    this.data = null;
   }
 }));

 constructor(private loader: LoaderService) { ... }

 <ng-container *ngIf="loading$ | async; else errorOrLoading">

 ...

 </ng-container>

,

简而言之,不。这是不可能的。这也不是一个好主意,因为这样做会破坏封装。

您也不能动态更改函数调用。如果doADance()为您跳舞,那么您实际上也无法动态地使其添加数字列表。函数的实现与其调用分开。尽管就您的观点而言,JavaScript实际上确实使人们使函数通过绑定不同的上下文等来动态地执行奇怪的事情。

RxJS还将实现与流的调用(订阅)分开。如果一个库进行了20次转换并将该流返回给您,则您不会真正获得转换列表,这只是库编写者可以在不引入重大更改的情况下进行更改的实现细节。

更新1:

是的,封装很重要,并且存在是有原因的。但是,我们 当然可以通过传递动态将数字列表添加到doADance() 该列表作为参数。也许是一种允许某种方式的受控方式 动态填充给定管道内的占位符 运算符会是一样的吗?

pipe中的占位符实际上没有任何意义,因为任何可管道运算符都容易转换为静态运算符,而任何一组运算符都容易转换为单个运算符。

您可以做的事情非常接近。例如,这不适用于从库返回的流,但是您可以设计流以允许自定义其处理方式。

为您服务:

function dataOnChange(): Observable<Data>{
  return myService.onChanges$.pipe(
    switchMap(() => this.getBackendData())
  );
}

function dataOnChangePlus(op): Observable<Data>{
  if(op == null) return dataOnChange();
  return myService.onChanges$.pipe(
    op,switchMap(() => this.getBackendData())
  );
}

其他地方:

this.service.dataOnChangePlus(
  tap(_ => this.data = null)
).subscribe(console.log);

在其他地方,做相同的事情,但有所不同:

this.service.dataOnChangePlus(
  st => st.pipe(
    mapTo(null),tap(val => this.data = val)
  )
).subscribe(console.log);

现在向dataOnChangePlus的使用者返回一个流,并且还可以帮助定义该流的构造方式。它不是动态添加运算符,但确实允许您推迟运算符的定义。

好处是每个调用都可以定义不同的内容。

如果需要,可以通过仅允许呼叫者访问特定类型的话务员来缩小呼叫者的工作范围。例如,仅让他们为tap运算符定义lambda:

function dataOnChangePlusTap(tapper): Observable<Data>{
  return myService.onChanges$.pipe(
    tap(tapper),switchMap(() => this.getBackendData())
  );
}

this.service.dataOnChangePlusTap(
  _ => this.data = null
).subscribe(console.log);