Your Location is: Home > Javascript

Ember.js not updating model

From: Spain View: 1705 goffreder 

Question

FINAL UPDATE: Kalman Hazins pointed me in the right direction, the computed property wasn't being called because of the fact that it wasn't rendered onscreen, so it wasn't "necessary" to re-compute it. I'll include here the final code for the controller:

App.QuestionController = Ember.ObjectController.extend({
    isEditing : false,

    resetIsEditing : function() {
        this.set('isEditing', false);
    }.observes('model.id'),

    canEditQuestion : function() {
        return this.get('author.id') === App.currentUser;
    }.property('author.id')
}

UPDATE: a "nice enough" solution has been provided by Raymond Liu below , it doesn't really answer the question "why this is happening", but it's the best I've got so far.


I'm learning Ember.js, follwing this book and making some changes. In my controller I have a property bound to the model, but when I change such model the property is not updated.

This is the controller:

App.QuestionController = Ember.ObjectController.extend({
    isEditing : false,

    canEditQuestion : function() {
        this.set('isEditing', false);
        return this.get('author.id') === App.currentUser;
    }.property('model')
});

This is the template:

<script type="text/x-handlebars" id="question">
    {{#if isEditing}}
        <!-- edit question form -->
    {{else}}
        <p id="question">{{question}}</p>
        {{#if canEditQuestion}}
            <a href="#" {{action "toggleEditQuestion"}}>Edit question</a>
        {{/if}}
    {{/if}}
</script>

Note that if I move the {{else}} branch content before {{#if isEditing}} it works as expected (but not how I want to).

Maybe the fact that question is a nested route matters:

App.Router.map(function() {
    this.resource('questions', function() {
        this.resource('question', { path : '/:question_id' });
    });
});

What I want is that even if I'm already editing a question, if I change model the isEditing property should go back to false and the question form is not shown. Apparently I can solve it if I always render the question, but that's not what I want. What am I missing?

EDIT: I'm adding the code for the question and the user model:

App.Question = DS.Model.extend({
    title : DS.attr('string'),
    question : DS.attr('string'),
    date : DS.attr('date'),
    author : DS.belongsTo('user', { async : true }),
    answers : DS.hasMany('answer', { async : true }) 
});

App.User = DS.Model.extend({
    fullname : DS.attr('string'),
    email : DS.attr('string'),
    questions : DS.hasMany('question', { async : true })
});

The App.currentUser property is set in the sign_in controller:

App.SignInController = Ember.Controller.extend({
    needs : [ 'application' ],

    actions : {
        signIn : function() {
            var email = this.get("email");
            var userToLogin = App.User.FIXTURES.findBy("email", email);

            if(userToLogin === void 0) {
                alert("Wrong email!");
                this.set("email", "");
            }
            else {
                localStorage.currentUser = userToLogin.id;
                App.set('currentUser', userToLogin.id);
            }
        }
    }
});

PS: the complete code for my app is available at https://github.com/goffreder/emberoverflow

PS2: I managed to get a functioning jsFiddle of my app. To reproduce you have to sign in using '[email protected]' email. Then, if you click on the first answer, you should see an 'Edit question' link that toggles the question form. Now, with that form opened, if you change question the form will still be availabe, with the new question as content, which is the behaviour I want to avoid.

Best answer

@goffredder and @Raymond Liu, the reason for canEditQuestion function/property not firing is as follows.

In Ember.js, the (computed) property evaluation is "magical", but as we all know - "magic" can be expensive. So, the value of a computed property gets cached, so that "magic" doesn't have to happen all the time and unless something changes that cached value doesn't get recomputed. Not only that, if the property is not being shown on the screen - why bother recomputing it? See this jsbin to see exactly what I am talking about. Notice how lazyPropDep never gets updated since lazyDep property is never shown on the screen.

Now, back to your example. canEditQuestion property is not being rendered to the screen when you are editing the question (since it's in the else block). Therefore, the canEditQuestion function which resets isEditing to false never gets called.

I think what you need is to place the resetting logic into an observer while leaving the other logic in the property as follows:

resetIsEditing : function() {
  this.set('isEditing', false);
}.observes('model.id'),

canEditQuestion : function() {
  return this.get('author.id') === App.currentUser;
}.property('model'),

This way EVERY TIME a new model (with a new model id) gets loaded into the controller (even if the previous model was in edit mode) the observer code will trigger resetting isEditing property to false

Another answer

update: I misunderstood your problem. Your want to disable form when go to another question, right? In your question_routs.js file, add this:

App.QuestionRoute = Ember.Route.extend({
  actions: {
    didTransition: function() {
      this.controller.set('isEditing', false);
      return true; // Bubble the didTransition event
    }
  }
})

These code will set 'isEditing' property to false when you transtion to another question. I noticed that the canEditQuestion function/property will not be called when toggle to another question just if isEiting property is set to true, I think that's the very problem need to be solved. And I have no idea why.

Another answer

It looks as though your computed property depends on two variables, author.id and App.CurrentUser that are not listed in the list of dependent keys. Instead you only listed model as a dependent key. I don't know what the relationship is between author and model, but you should list the actual properties/variables that you need in order to compute your property.

Another answer

You can do smthing like:

<script type="text/x-handlebars" id="question">
 {{#unless isEditing}}
    <p id="question">{{question}}</p>
    {{#if canEditQuestion}}
        <a href="#" {{action "toggleEditQuestion"}}>Edit question</a>
    {{/if}}
 {{else}}
    <!-- edit question form -->
 {{/unless}}
</script>