Here are 4 possible ways.
1. Utility function that returns a list of operators
If you want to reuse it between contexts, you can try making a function that accepts the thisArg and returns an array of operators.
Then you can spread the invoked function in the argument passed to 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));
Note: terminologyService and sabs would need to be present on the this context you pass to this function.
It looks like you are using these for components, so as long as terminologyService is being injected as a dependency and sabs is a static member of the component, it should work.
2. Utility function that returns composed operators
Alternatively, you could accomplish the same this by importing pipe from rxjs to chain the operators together.
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);
})
);
In this case, you do not need to use the spread operator since the operators are composed together using pipe's left-to-right function composition.
// subject A
this.codeSearchResults$ = this.codeInput$
.pipe(typeAhead(this));
// subject B
this.articleSearchResults$ = this.articleInput$
.pipe(typeAhead(this));
Note: terminologyService and sabs would need to be present on the this context you pass to this function.
3. Service for custom operators
You could make a reusable service for your custom piped operators. This would allow you to grab the terminologyService singleton from the reusable service itself.
However, it looks like sabs will still need to be available from whereever it is coming from.
If you choose to do this, make sure to declare TerminologyService in the top level providers - see example here
Then you could inject it into your components.
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);
})
);
}
}
Then you can use it in a component:
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)
);
}
}
Note: sabs would need to be present on the this context you pass to this function. I made a dummy one here.
4. Service for custom operators (no this context)
If you want to make this truly reusable and not have to worry about this, you can just compose the operators you need that do not require a this context.
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),
);
}
}
Then be sure to add the specific operators you need in the component:
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);
})
);
}
}
Note: sabs would need to be present on the this context you pass to this function. I made a dummy one here.
It's been a while since I've used angular. I hope some of these examples are useful.