Detect change from input

Hey

We have a lot of issues with change detection. If user changes a value in select, change event is fired and some magic happens. But value can also be changed from view model logic without user input and then change event and its magic is fired as well. But this magic should only be executed if user changes input value on model view. We have absolutely no clue how to detect if change is fired by user input or automatically from view model.

Here some example

<!-- model view -->
<template>
  <select id="my-fancy-select" value.bind="selectedValue">
    ...
  </select>
</template>
// view model
  public selectedValueChanged(): void {
    //this magic must only be executed if 'selectedValueChanged' is user changes 'selectedValue' on model view
    //but it's also executed if 'selectedValue' is changed by some view model logic.
    this.executeMagic();
  }

Thanks in advance

Just encapsulate it and make the setter different from the view model calls.

<select id="my-fancy-select" value.bind="selectedValueInView">
    ...
 </select>
public selectedValue: string;
public get selectedValueInView(){
 return this.selectedValue;
}
public set selectedValueInView(val){
  this.selectedValue = val;
  this.selectedValueInViewChanged()
}

public selectedValueInViewChanged(): void {
    //this magic must only be executed if 'selectedValueChanged' is user changes 'selectedValue' on model view
    //but it's also executed if 'selectedValue' is changed by some view model logic.
    this.executeMagic();
  }
4 Likes

@Kukks You’re my hero :laughing:

I did a first test and it seems to work. Thanks a lot :+1:.

Issue is solved.

Wouldn’t you need computedFrom to notify ui if the backing field changes?
I wish this technique worked for a bindable property…

There is another natural way to tap into a binding which is value converter.

This will generically work on any binding. I am writing on my phone, hopefully don’t make too many mistakes.

<select value.bind="selected | onSet:myCallback"></select>
constructor() {
  // there is probably something we can do in value converter to avoid this line
  this.myCallback = this.myCallback.bind(this);
}
myCallback(newValue) {
}
export class OnSetValueConverter {
  toView(value) { return value; }
  fromView(value, cb) {
    if (typeof cb === 'function') cb(value);
    return value;
  }
}

You can also tap in both way if you want "selected | tap:onGet:onSet" with another value converter.

1 Like

Definitely an alternative, although still does not cover catching UI changes on bindable properties of custom elements. I figured two options, first is good old suppress flags, second tapping into binding itself, replacing updateTarget function.

@MaximBalaganskiy I might missed your point, but this should work on custom element with two-way binding. Kukks’s method should work with custom element too.

<some-element value.bind="value | onSet:cb"></some-element>

That won’t work inside a custom element where you don’t control external binding, which might not even exist

Still not clear about what is your use case.

If you have a SomeElement has bindable prop value, you can use <input value.bind="value | onSet:cb"> in its template and have a cb() in SomeElement class.

When an element has a non text property, say it’s an autocomplete with an object value, you can’t just bind a dummy input to it, yet you need to differentiate an external change from the internal change

change.delegate?
would only go one way, no?

You are in the element’s view model code and have no control over how a customer binds to it. How do you differentiate a change from ui? Flags or tap into bindings.

I’d love to see an example…
We are talking about input right?
So either you have it inside your component… and can do what you want with it.
Or it is external to you and it should not matter how the change came in.
Otherwise it’s tight coupling IMO.

Let’s say we’ve got an <autocomplete value.two-way="someObject"></autocomplete>
Most of the time someObject will be set by the component upon selecting an option in a dropdown. But also the selected value will be set by a external user of an element. Inside the component’s code these assignments might need to be differentiated.

This doesn’t introduce any problem for either my solution or @Kukks’s solution. You just need to try in your code.

The onSet callback cb(newValue) is only triggered by changes from autocomplete sub-component, not current component code.

<autocomplete value.two-way="someObject | onSet:cb"></autocomplete>

This depends on a client to use a converter. I was talking about detecting a change inside the element.

Don’t know the exact use case but change.delegate only fires on user interaction.