I want to display a filtered list of strings for display to the user where the filter is a function of input from the user and another (dynamically changing) array which contains strings that should not be displayed. For context, the goal is to have an autocomplete list that is filtered by the user input and doesn’t included strings that the user did already select previously.
Currently I have the following which works to update the filtered list in case the user input changes but doesn’t update it when the content of the already selected strings changes. And also not when the allStrings
array changes.
allStrings: string[] = ["Lemon", "Apple", "Orange"];
selectedStrings: string[] = [];
userInput = new FormControl<string>('');
filteredStrings!: Observable<string[]>();
ngOnInit() {
this.filteredStrings = this.userInput.valueChanges.pipe(
startWith(""),
map(input => {
return this.allStrings.filter(item => {
return input == "" || (item.includes(input) && !this.selectedStrings.includes(item));
});
}),
);
}
How can I achieve what I want? Convert allStrings
and selectedStrings
to observables (or subjects?) and use combineLatest
or similar to trigger on changes of all inputs? Or is there are more idiomatic way of doing this kind of thing in Angular/RxJS?
allStrings!: Observable<string[]>;
selectedStrings!: Observable<string[]>;
userInput = new FormControl<string>('');
filteredStrings!: Observable<string[]>();
ngOnInit() {
this.filteredItems = combineLatest(this.allStrings, this.selectedStrings, this.userInput.valueChanges).pipe(
startWith([[], [], ""]),
map([all, selected, input] => {
return all.filter(item => {
return input == "" || (item.includes(input) && !selected.includes(item));
});
}),
);
}
Your approach of using combineLatest
looks promising. It allows you to react to changes in all three inputs: allStrings
, selectedStrings
, and userInput
. This way, the filteredItems
observable will be updated whenever any of these inputs change.
Here’s the adjusted code:
allStrings$: Observable<string[]>;
selectedStrings$: Observable<string[]>;
userInput = new FormControl<string>('');
filteredItems$: Observable<string[]>;
ngOnInit() {
this.filteredItems$ = combineLatest([this.allStrings$, this.selectedStrings$, this.userInput.valueChanges]).pipe(
startWith([[], [], ""]),
map(([all, selected, input]) => {
return all.filter(item => {
return input == "" || (item.includes(input) && !selected.includes(item));
});
}),
);
}
This code assumes that allStrings
and selectedStrings
are observables. If they are regular arrays that can change over time, you’ll need to convert them to observables using BehaviorSubject
or ReplaySubject
.
Remember to replace this.allStrings
and this.selectedStrings
with actual observables or subjects.
This setup should work for dynamically updating the filtered list based on changes in user input, allStrings
, and selectedStrings
. Keep in mind that you’ll need to subscribe to filteredItems$
in your template or component logic to consume the filtered list.