ember.js: `notifyPropertyChange` does not propagate from controller to component

Demo: http://emberjs.jsbin.com/xufitu/3/edit?html,js,console,output

    {{foo-bar myProperty=myProperty}}
    <button {{action "trigger"}}>Trigger</button>
App = Ember.Application.create();

App.IndexController = Ember.Controller.extend({
  myProperty: { zomg: 'lol' },

  report: function() {
    Em.debug('ConTROLLer: property did change!');
  }.observes('myProperty'),

  actions: {
    trigger: function() {
      this.notifyPropertyChange('myProperty');
    }
  }
});


App.FooBarComponent = Ember.Component.extend({  
  myProperty: null, 

  report: function() {
    Em.debug('ComPONent: property did change!');
  }.observes('myProperty')
});

When i trigger the controller action, i expect both observers to fire.

In reality, only the controller’s observer fires and the component’s does not.

dafuq

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 25 (19 by maintainers)

Most upvoted comments

This is exactly the same “issue” as #10405. The scare quotes are because it is a misuse of the object system. And this should be resolved with the new “data down” model.

TL;DR: If you call set on a property AND it is an object AND it is === to the existing value then observers will not be fired. Here’s the code. There are also other similar ways observers skip being fired involving chain nodes, etc.

Here is the sequence of events. I hope this helps you debug in the future.

  • The helper call for {{foo-bar myProperty=myProperty}} executes.
    • The foo-bar component is created with a stream binding for myProperty.
      • The component begins initializing.
      • All stream bindings are connected.
        • The stream binding calls set(component, "myProperty", get(controller, "myProperty)). Note that this doesn’t trigger observers because objects never fire observers during the init phase.
        • Set up observers on each side that sync the value back and forth between component and controller.
      • The component is finished initializing.
      • The stream binding is gets the value from the context (controller) and set
    • The component is finished instantiating.
  • controller.notifyPropertyChange("myProperty") is called.
    • All the observers for myProperty on the controller fire.
      • The report observer on the controller fires.
      • The myProperty stream binding observer fires.
        • The binding schedules to sync on the sync queue, but I will omit the details of this since it’s not relevant.
        • The stream binding calls set(component, "myProperty", get(controller, "myProperty)). But the value hasn’t changed! So the observers for myProperty on the component are not notified as per the TL;DR above.