Here is my code (with renamed methods) from Angular:
I have a function interface/contract
type IAutocomlete = (
term: string,
selectedIds?: string[]
) => Observable<TableFilterQueryResponse[]>;
This is my draft service that I want to inject in my Components in the future. I will have more methods with similar implementation.
export class SuperCleanService {
constructor(
private dataController: DataController,
private serviceController: ServiceController
) {}
.....
private searchAs$: IAutocomlete = (term, selectedIds) => {
return this.dataController
.service1GET(term, selectedIds)
.pipe(
map((res) =>
res.map((a) => ({
viewValue: a.name,
value: a.id
}))
)
);
};
private searchB$: IAutocomlete = (term, selectedIds) => {
return this.serviceController
.service1(term, selectedIds)
.pipe(
map((res) =>
res.map((a) => ({
viewValue: a.id,
value: a.id
}))
)
);
};
private searchC$: IAutocomlete = (term, selectedIds) => {
return this.dataController
.service2GET(term, selectedIds)
.pipe(
map((res) =>
res.map((a) => ({
viewValue: a.name,
value: a.id
}))
)
);
};
...
}
How can I refactor (following DRY rule) and make it as clean as possible? My main goal is to avoid using map in each function.
Consider creating a custom operator that houses the reusable logic.
custom-pipe.ts
import { pipe, of } from "rxjs";
import { startWith, switchMap } from "rxjs/operators";
export function myCustomOperator() {
return pipe(
map((res) =>
res.map((a) => ({
viewValue: a.name,
value: a.id
}))
)
);
}
Your component will become:
export class SuperCleanService {
constructor(
private dataController: DataController,
private serviceController: ServiceController
) {}
.....
private searchAs$: IAutocomlete = (term, selectedIds) => {
return this.dataController
.service1GET(term, selectedIds)
.pipe(
myCustomOperator()
);
};
private searchB$: IAutocomlete = (term, selectedIds) => {
return this.serviceController
.service1(term, selectedIds)
.pipe(
myCustomOperator()
);
};
private searchC$: IAutocomlete = (term, selectedIds) => {
return this.dataController
.service2GET(term, selectedIds)
.pipe(
myCustomOperator()
);
};
...
}
In this case I would use generics with wrapper method which accepts autocomplete function and maps to desired result.
interface Autocomplete {
id?: string;
name?: string;
}
type IAutocomplete = (
term: string,
selectedIds?: string[]
) => Observable<TableFilterQueryResponse[]>;
type AutocompleteCallback<T> = (
term: string,
selectedIds?: string[]
) => Observable<T[]>;
...
// use instance arrow function to preserve 'this' in callback method
private autocompleteMapper = <T extends Autocomplete>(
callback: AutocompleteCallback<T>,
term: string,
selectedIds: string[]
) => {
return callback(term, selectedIds).pipe(
map((res) =>
res.map((a) => ({
viewValue: a.name,
value: a.id
}))
)
);
};
private search1$: IAutocomplete = (term, selectedIds) => {
return this.autocompleteMapper<ResponseInterface>(
this.dataControllerService.service1GET,
term,
selectedIds
);
};
...
``
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With