How to properly get informations depend on Component itself?

I know DDAU, I know Components should not mutate data it receives, but what if data comes from Component itself? How should I properly set them and pass them to other components? (maybe to nested children or irrelevant components)

For demonstration, let’s see some code:


export default Ember.Component.extend({
  didRender() {

    // 1. didRender is required, because I need the DOM existed
    // 2. didInsertElement could be used instead, but we often need to do
    // things after each rendering, UI is changing constantly.

    let clientRect = this.element.getBoundingClientRect();

    // Now we can get width/height/top/right/bottom/left, these are the
    // data we really want

    // If children components need these data (depends on parent's DOM position)
    // we want to pass them down, so we set them as property(ies)

    this.set('positions', clientRect)

  }
})

This demo shows what I need to say very straight forward, but it did in a wrong way.

First, Ember will warning like this:

A property of <YourApp@view:-outlet::emberXXX> was modified inside the didUpdate hook.
You should never change properties on components, services or models during didUpdate
because it causes significant performance degradation.
[deprecation id: ember-views.dispatching-modify-property]

It is reasonable (even it tells you didUpdate instead of didRender), I’ve already been suffered with this.

But even I can understand that, I still don’t know how to improve that. It is required to use didRender because the DOM has to be ready; it is required to set/mutate data as properties because other components need them, DDAU can’t work here because the data we need is actually comes from the component itself.

Someone suggested to use CP, I don’t understand what is the difference here, maybe I’m stupid but what I can imagine is something like this:

export default Ember.Component.extend({

  // Ok, we define a CP, but what things to look over?
  positions: Ember.computed(/* what? */, {
    get() { 
      return /* what? */
    }
  }),

  didRender() {

    // Still need to do here, right?
    let clientRect = this.element.getBoundingClientRect();

    // then, how to tell `positions` to compute itself?

  }

})

What’s your suggestion? please enlighten me, I can’t solve this problem for days, really makes me crazy now.

I just realized that you don’t have to watch something for computed property, you just need to use it, use it as a property like pass it down to another component or render it in a template, it will gives you the value you need!

How foolish I am?!

I have to say, official document doesn’t point out this, all examples looks like CP need to watch something. But I’m also worried, when something changed, like user move it in UI, change its positions, how CP knows that change and re-calculate itself again?

It gets recomputed when what it is watching changes. If it watches nothing, it is never recomputed.

Well the details are a bit more complex:

  • It does not actually get recomputed immediately. It gets invalidated (the computed property cache is cleared).
  • It does not necessarily mean what it is watching changed. It means it was set, but if it was set to the value it already has, invalidation happens all the same.
  • It then gets recomputed when the value is required.

Can you detail more what you are trying to accomplish? Like, what do the sub-components do with those values?

Well, that’s complicated, let me break it down:

  1. we have some timeline columns, each of them has time scales equably distributed from top to bottom, start from 00:00 to 24:00, and this can be customized by user’s setting, like from 10:00 to 22:00.

  2. each timeline column has varied amount of events, they are the sub-components.

  3. each event have its own time range, like start: 1:20, end: 3:40.

  4. by some calculation, we figure out each event’s relative height and top position, then we know where it sit in the timeline column.

  5. user can edit these event and change the time range, event components will re-rendered, and the height and/or position will change also.

  6. each event has a sub-component, let’s call it detail. because the time column’s width is limited, we can show too many event’s information, so we decide to let users click it to pop out a detail panel.

  7. since events can be placed anywhere, top-left/right or bottom-left/right, so the detail panel should adjust its position to make sure not be blocked by the edges of browser window.

  8. in addition, the detail panel’s height should be as same as its parent (event), this is a requirement from our client. If content’s length exceed the height, we need to allow user scroll up and down in this little panel.

  9. now, to set the proper position and height for detail panels (children), we need to pass the relevant information from events (parents), and these information can be changed as described in 5.

this is the problem we are facing, we actually already done it, but ember throw a lot of warnings to us, I understand that but don’t know how to improve that.