Search code examples
ember.jsember-components

emberjs two-way-binding temporarily broken after value assignment at init


I am trying to assign default values for one of my components at init lifecycle hook; in case an undefined value is passed from parent component. Initially everything seems to be working as expected; however when my component is forced a re-render (possibly via another property value update at parent component); undefined value at parent component is written back to my component.

This means; the value assignment I made during initialization is not reflected to parent component, in other words two-way-binding is temporarily not working (the values of parent and child components are not synchronized). Is it the expected behavior or am I missing sth. important about init event? Where is the appropriate place to initialize undefined values of a component? See the twiddle for a simple illustration.


Solution

  • Okay, first a way to work around is to use the update function on the attr. Checkout this twiddle.

    I replaced this.set('name', 'tom') with this.attrs.name.update('tom') in the .js and {{name}} with {{attrs.name}} in the .hbs.

    Another way to work around is just to wrap the asignment in a Ember.run.later like I've done here, where I replaced this.set('name', 'tom') with that:

    Ember.run.later(() => {
      this.set('name', 'tom');
    });
    

    So that are the workarounds.

    The fact that the bindings are not completely set up on init has a long history. But generally its an anti pattern to initialize a value in a child component and give this value up to the parent. It makes your code less readable and is agains the DDAU (Data down, Actions up) principle.

    I recommend to initialize the data explicit when you create your models, not implicit during your component evaluation cycle.

    For default values that you don't want to store I recommend you to write a computed property:

    nameWithDefault: computed('name', {
        get() {
            return get(this, 'name') || 'tom';
        },
        set(key, val) {
            set(this, 'name', val);
        }
    })
    

    This is explicit, does not write down the data after a component is viewed, and works for the user.