在 RxJS 中重用可管道运算符

Bra*_*don 8 rxjs

我在 Angular 组件中有两个主题,它们利用同一组可管道运算符为两个不同的表单字段提供预输入搜索查找。例子:

this.codeSearchResults$ = this.codeInput$
                               .pipe(
                                 untilDestroyed(this),
                                 distinctUntilChanged(),
                                 debounceTime(250),
                                 filter(value => value !== null),
                                 switchMap((value: string) => {
                                   const params: IUMLSConceptSearchParams = {
                                     ...TERMINOLOGY_SEARCH_PARAMS,
                                     sabs: this.sabs,
                                     term: value
                                   };

                                   return this.terminologyService.umlsConceptSearch(params);
                                 }),
                               );
Run Code Online (Sandbox Code Playgroud)

管道的定义似乎它将接受任意数量的函数,但是通过扩展提供函数

this.codeSearchResults$ = this.codeInput$.pipe(...operators);
Run Code Online (Sandbox Code Playgroud)

没有按预期工作。我如何为两个主题提供单一的函数输入源以保持我的代码干燥?

编辑

根据 Dan Kreiger 的回答中的选项#2,我的最终代码如下:

const operations = (context) => pipe(
      untilDestroyed(context),
      distinctUntilChanged(),
      debounceTime(250),
      filter(value => value !== null),
      switchMap(value => {
        const term: string = value as unknown as string;
        const params: IUMLSConceptSearchParams = {
          ...TERMINOLOGY_SEARCH_PARAMS,
          sabs: context.sabs,
          term,
        };

        return context.terminologyService.umlsConceptSearch(params) as IUMLSResult[];
      }),
    );

    this.codeSearchResults$ = this.codeInput$
                                  .pipe(
                                    tap(() => this.codeLookupLoading = true),
                                    operations(this),
                                    tap(() => this.codeLookupLoading = false),
                                  ) as Observable<IUMLSResult[]>;

    this.displaySearchResults$ = this.displayInput$
                                      .pipe(
                                        tap(() => this.displayLookupLoading = true),
                                        operations(this),
                                        tap(() => this.displayLookupLoading = false),
                                      ) as Observable<IUMLSResult[]>;
Run Code Online (Sandbox Code Playgroud)

我需要编写几个tap()每个主题唯一的函数,并且它按预期工作。

Dan*_*ger 7

这里有 4 种可能的方法。

1. 返回运算符列表的实用函数

如果您想在上下文之间重用它,您可以尝试创建一个接受thisArg并返回运算符数组的函数。

然后您可以将调用的函数传播到传递给 的参数中pipe

/**
 * @param {object} thisArg - context using the typeahead
 * @returns {OperatorFunction[]}
 * 
 * list of pipepable operators 
 * that can have a dynamic `this` context
 */
const typeAhead = thisArg => [
  untilDestroyed(thisArg),
  distinctUntilChanged(),
  debounceTime(250),
  filter(value => value !== null),
  switchMap((value: string) => {
    const params: IUMLSConceptSearchParams = {
      ...TERMINOLOGY_SEARCH_PARAMS,
      sabs: thisArg.sabs,
      term: value
    };

    return thisArg.terminologyService.umlsConceptSearch(params);
  })
]


// subject A
this.codeSearchResults$ = this.codeInput$
  .pipe(...typeAhead(this));

// subject B
this.articleSearchResults$ = this.articleInput$
  .pipe(...typeAhead(this));
Run Code Online (Sandbox Code Playgroud)

注意: terminologyService并且sabs需要出现在this您传递给此函数的上下文中。

看起来您正在将它们用于组件,因此只要terminologyService作为依赖项注入并且sabs是组件的静态成员,它就应该可以工作。


2. 返回组合运算符的实用函数

pipe或者,您可以通过导入fromrxjs将运算符链接在一起来完成相同的操作。

import { pipe } from "rxjs";

// ... 

const typeAhead = (thisArg) =>
  pipe(
    untilDestroyed(thisArg),
    distinctUntilChanged(),
    debounceTime(250),
    filter((value) => value !== null),
    switchMap((value: string) => {
      const params: IUMLSConceptSearchParams = {
        ...TERMINOLOGY_SEARCH_PARAMS,
        sabs: thisArg.sabs,
        term: value
      };

      return thisArg.terminologyService.umlsConceptSearch(params);
    })
  );
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您不需要使用扩展运算符,因为运算符是使用 的pipe从左到右的函数组合组合在一起的。

// subject A
this.codeSearchResults$ = this.codeInput$
  .pipe(typeAhead(this));

// subject B
this.articleSearchResults$ = this.articleInput$
  .pipe(typeAhead(this));
Run Code Online (Sandbox Code Playgroud)

注意: terminologyService并且sabs需要出现在this您传递给此函数的上下文中。


3. 定制运营商服务

您可以为自定义管道运算符提供可重用的服务。terminologyService这将允许您从可重用服务本身获取单例。

然而,无论它来自哪里,看起来sabs仍然需要可用。

TerminologyService如果您选择这样做,请确保在顶级提供程序中声明- 请参阅此处的示例

然后你可以将它注入到你的组件中。

import { TerminologyService } from "./terminologyService.service";
import { Injectable } from "@angular/core";
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';


@UntilDestroy()
@Injectable({
  providedIn: 'root',
})
export class PipedOperatorsService {
  constructor(private terminologyService: TerminologyService) {}

  typeAhead(thisComponent) {
    return pipe(
      untilDestroyed(thisComponent),
      distinctUntilChanged(),
      debounceTime(250),
      filter((value) => value !== null),
      switchMap((value: string) => {
        const params: IUMLSConceptSearchParams = {
          ...TERMINOLOGY_SEARCH_PARAMS,
          sabs: thisComponent.sabs,
          term: value
        };

        return this.terminologyService.umlsConceptSearch(params);
      })
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在组件中使用它:

import { Component, OnInit } from "@angular/core";
import { PipedOperatorsService } from "./pipedOperators.service";
import { Observable } from 'rxjs';

@Component({
  selector: "some-root",
  templateUrl: "./some.component.html",
})
export class SomeComponent implements OnInit {
  sabs = ['What', 'is', 'a', 'sab', '?', '']; 
  codeSearchResults$: Observable<string[]>;

  constructor(private pipedOperatorsService: PipedOperatorsService){}

  ngOnInit() {
    this.codeSearchResults$ = this.codeInput$.pipe(
      this.pipedOperatorsService.typeAhead(this)
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

注意: sabs需要出现在this您传递给此函数的上下文中。我在这里做了一个假人。


4. 自定义操作员服务(无this上下文)

如果您想让它真正可重用并且不必担心this,您可以编写不需要上下文的所需this运算符。

import { Injectable } from "@angular/core";
import { pipe } from "rxjs";
import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'


@Injectable({
  providedIn: 'root',
})
export class PipedOperatorsService {    
  get typeAhead() {
    return pipe(
      distinctUntilChanged(),
      debounceTime(250),
      filter((value) => value !== null),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

然后一定要在组件中添加您需要的特定运算符:

import { Component, OnInit } from "@angular/core";
import { TerminologyService } from "./terminologyService.service";
import { PipedOperatorsService } from "./pipedOperators.service";
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';


@UntilDestroy()
@Component({
  selector: "some-root",
  templateUrl: "./some.component.html",
})
export class SomeComponent implements OnInit {
  sabs = ['What', 'is', 'a', 'sab', '?', '']; 
  codeSearchResults$: Observable<string[]>;

  constructor(private pipedOperatorsService: PipedOperatorsService, private terminologyService: TerminologyService){}

  ngOnInit() {
    this.codeSearchResults$ = this.codeInput$.pipe(
      untilDestroyed(this),
      this.pipedOperatorsService.typeAhead,
      switchMap((value: string) => {
        const params: IUMLSConceptSearchParams = {
          ...TERMINOLOGY_SEARCH_PARAMS,
          sabs: this.sabs,
          term: value
        };

        return this.terminologyService.umlsConceptSearch(params);
      })
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

注意: sabs需要出现在this您传递给此函数的上下文中。我在这里做了一个假人。

我已经有一段时间没有使用 Angular 了。我希望其中一些示例有用。