Currently, I am working on a view containing product list and I have some trouble with filtering this list.
My view has a fetch
method, which should be called to fetch the filtered product list from my backend API. I first created a general search field that calls the fetch
method in combination with debounce in a keyup
event delegate: <input type="text" id="search" value.bind="search" keyup.delegate="fetch() & debounce">
. The viewmodel’s search
variable is marked as @observable
and the searchChanged
callback function updates the view’s URL in the browser as well. This all works splendid.
So far so good. Next, I created some additional filter options (checkboxes and radios) and that’s where the trouble starts. I want the product list to update immediately when a filter option is changed, but I seem to get stuck in tricky data binding behavior in combination with event delegates.
I tried to isolate the tricky behavior in a small app:
app.html
<template>
<h3>Filters</h3>
<div>
<input type="checkbox" id="option1" checked.bind="option1" click.delegate="fetch()">
<label for="option1">Filter option 1</label>
</div>
<div>
<input type="checkbox" id="option2" checked.bind="option2" click.delegate="fetch()">
<label for="option2">Filter option 2</label>
</div>
<h3>Results</h3>
<div>
<span>${result}</span>
</div>
</template>
app.ts
export class App {
option1 = false;
option2 = false;
result = 'No results';
async fetch() {
if (this.option1) {
if (this.option2) {
this.result = await Promise.resolve('Showing results for both filter options set');
}
else {
this.result = await Promise.resolve('Showing results for only filter option 1 set');
}
}
else if (this.option2) {
this.result = await Promise.resolve('Showing results for only filter option 2 set');
}
else {
this.result = await Promise.resolve('Showing results for no filter options set');
}
}
}
After checking/unchecking options, the result message is incorrect. It seems that the result message always reflects the previous state, not the current state. So it seems that the data binding updates take place after handling the click
event delegate. Inside the fetch
method, the this.option1
and this.option2
variables still hold the old values.
Note that that seems to be in contrast with the vanilla HTML/Javascript behavior: the actual checkbox input elements themselves already have the updated state when the fetch()
method starts executing. I verified this using a ref
binding and checking the element state inside the fetch()
method.
I imagine there are several solutions to fix this behavior. I already recognized the following possible solutions:
- Removing the click delegates from the HTML markup, marking the
option1
andoption2
backing variables as@observable
and calling thefetch()
method from theoption1Changed
andoption2Changed
callback functions. - Adding the
debounce
binding behavior with a delay of 0 ms to the binding expression in HTML:click.delegate="fetch() & debounce:0"
.
Are these the only straightforward solutions or are there any better and/or more elegant solutions available? Regarding the above solutions, the first one seems to be acceptable, but I consider the second one to be pretty ‘hacky’: it abuses the debounce
binding behavior to force a setTimeout
call (or something alike) so that the data binding mechanism gets a chance to execute before fetch()
is called.
I tend to settle with the first solution. Do you agree? Or are there any other solutions that would be recommended in a scenario like this?