Is my Ember.observer causing an issue in ember-bootstrap?

I have a component that takes an HH:MM and DD/MM/YYYY input and creates a date object as a value to be handled by my ember-bootstrap form.

Capture

The component produces the value one of three ways:

  • An ‘Insert Time’ button click that triggers an action populating the fields, setting the value
  • Manual input of the HH:MM, DD/MM/YYYY fields, triggering the action to set the value based on the input
  • If a dropdown menu earlier in the form is selected, the time auto-populates to save the user some faffing.

Case 1 and 2 work fine. The problem is in case 3.

Here is the invocation within the form:

        {{#form.element class="form-label-bold" label="Time of Clerking" property="time_of_clerking" as |el|}}
            <DateTimeProForma @value={{el.value}} @genTime={{presentation.isSenior}} @existing={{model.time_of_clerking}} />
        {{/form.element}}

The relevant code from the component:

// if the isSenior property changes, the observer is supposed to execute the inserTime function from the actions
  triggerResponse: observer('genTime', function () {
    if (this.genTime === true) {
      let execute = this.actions
      execute.insertTime(this)
      this.set('genTime', false)
    }
  }),

  actions: {
    insertTime(context, existing) {
      let self = context || this

    //there is some code here to deal with the inputs and get them ready to be turned into an object

      self.set(`hours`, `${hours}:${minutes}`);
      self.set(`date`, `${day}/${month}/${generatedDate.getFullYear()}`);

// this sets the value property to the result of the createDateString Function
      self.set('value', createDateString(self.get('hours'), self.get('date')))

    }
  }

This is the action that triggers when a user clicks the ‘insertTime’ button and it works great. The general idea in Case 3 is that when the presentation.isSenior field changes, the observer executes the insertTime function as if a user has clicked the ‘Insert Time’ button.

It mostly works! The hours, date and even the value properties of the component all update as expected however in this one specific the case, the value on the component isn’t added as the value of the property by ember-bootstrap.

I don’t have this issue with Case 1 or Case 2 and I’m going slightly mad trying to work out why Case 3 has the problem when all things seem to be identical.

Given that all other things are equal, I assume the problem is something to do with the use of an observer and it producing unexpected behavior in ember-bootstrap. I’ve been going slightly insane trying to work this out and any ideas would be greatly appreciated.

TL;DR: Don’t use observers. Instead construct a component hierarchy that supports data down actions up.

In other words, have the source of truth yielded by a contextual component. Have the actions to change it also yielded. This way only one component actually does any mutations of the data. Then those changes will propagate down (yielded properties) and everything responds as expected.

Once you introduce an observer that entire process is flipped upside down and no one can rightly understand who owns the truth. An observer is only useful if it is the very last thing in Ember land before moving into some other third part library. The way I look at it is that Ember’s templating code does the observing and I should never have to unless I can no longer use Ember’s templating like in the case of a jQuery plugin where I am forced to do the observing at that seam.

Now, after some investigation into ember-bootstrap it seems they failed to follow this advise. Their API promotes two-way bindings :scream: This is just wrong but changing ember-bootstrap is a bigger boulder then any of us is willing top push.

Instead I would recommend making an integration plugin (as they call it):

If you are using the custom control quite often, you should consider writing an integration plugin like ember-bootstrap-power-select . To do so, you need to provide a component {{bs-form/element/control/my-custom-control}} which extends Components.FormElementControl .

Doing this would allow you to control the data flow and avoid the squirrely nature of observers. It would also consolidate where the source of truth is.

Hi Sukima, thanks for your reply (once again!)

You are spot on about using a yielded property from a parent component. I actually already do this for another feature and I’m not sure why I didn’t apply the same method here. I think I got too focused on ‘The Observer’ and ended up with tunnel vision.

I’m still not sure why in this specific case ember-bootstrap sees the value through one method (clicking the button) and not the other (executing through the observer) but I assume the answer is in the mix between my code and how e-b updates.

As I already have my parent components that’s the answer I’ll pursue!