knockout.jsknockout-3.0computed-observable

Computed observable not updating UI


I have a problem with a computed variable in my knockout web. Here's my code:

function ViewModel() {
    var self = this;
    self.seatsRaw = ko.observableArray([]);
    self.selectedSeatId = ko.observable();
    self.session = ko.observable();
    self.seats = ko.computed(function(){
        var seatsComplete= self.seatsRaw().slice(0);
        for (var i = 0; i < seatsComplete.length; i++) {
            if (self.selectedSeatId()) {
                seatsComplete[i].selected = seatsComplete[i].id == self.selectedSeatId();
            } else if (self.session () && Number(self.session ().seat) > 0) {
                seatsComplete[i].selected = seatsComplete[i].id == self.sesion().seat;
            } else {
                seatsComplete[i].selected = false;
            }
        }
        return seatsComplete;
    });
    self.selectSeat = function(data,event) {
    self.selectedSeatId($(this).id);
    };
}

So, I just receive an array with seats and I want to add a field to each object in array to show in the UI as "selected".

I read from ajax the info for session and seats raw, and when I update them I don't see any change in my UI.

Why is happening? And then, what I need to do to update my UI?

Thanks!

EDIT

<ul class="seats" data-bind="foreach: seats">
      <li data-bind="css: {selected: selected, 'non-selected': !selected}, click: $root.selectSeat">
       <a href="#" data-bind="attr: {id: id}">
            <img data-bind="attr: {src: baseUrl + 'img/seats/' + image}" />
            <p data-bind="text: name"></p>
       </a>
      </li>
 </ul>

Seat is not a ViewModel... should it be?


Solution

  • Thanks for providing some extra context. I'll try to explain a bit better what I meant in the comments.

    Seat is not a ViewModel... should it be?

    Even when a Seat is just a plain object, it's also kind of a viewmodel... Here's what's happening in your code (as far as I can tell):

    When you're creating the new array in your computed, it gets filled with the same objects the previous array had. The knockout renderer doesn't see a reason to re-render the list items.

    Check out this example. From the logs you see everything "working correctly", but knockout won't update. After this example, I'll show what happens if we make selected observable.

    var seats =[
      { id: 1, selected: false },
      { id: 2, selected: true },
      { id: 3, selected: false }
    ];
    
    var ViewModel = function() {
      var self = this;
      this.rawSeats = ko.observableArray(seats);
      
      this.selectedSeatId = ko.observable(2);
      this.selectSeat = function(data, event) {
        self.selectedSeatId(data.id)
      }
      
      this.seats = ko.computed(function() {
        var selectedId = self.selectedSeatId();
        self.rawSeats().forEach(function(seat) {
          seat.selected = seat.id === selectedId;
        })
        
        console.log(self.rawSeats());
        return self.rawSeats();
        
      });
      
      }
    
    ko.applyBindings(new ViewModel());
    .selected { background: yellow }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <ul data-bind="foreach: seats">
      <li data-bind="css: { selected: selected }, 
                     text: id,
                     click: $root.selectSeat"></li>
    </ul>

    Now, with observable properties:

    var seats =[
      { id: 1, selected: ko.observable(false) },
      { id: 2, selected: ko.observable(true) },
      { id: 3, selected: ko.observable(false) }
    ];
    
    var ViewModel = function() {
      var self = this;
      this.rawSeats = ko.observableArray(seats);
      
      this.selectedSeatId = ko.observable(2);
      this.selectSeat = function(data, event) {
        self.selectedSeatId(data.id)
      }
      
      this.seats = ko.computed(function() {
        var selectedId = self.selectedSeatId();
        self.rawSeats().forEach(function(seat) {
          seat.selected(seat.id === selectedId);
        })
        
        return self.rawSeats();
        
      });
      
      }
    
    ko.applyBindings(new ViewModel());
    .selected { background: yellow }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <ul data-bind="foreach: seats">
      <li data-bind="css: { selected: selected }, 
                     text: id,
                     click: $root.selectSeat"></li>
    </ul>