Skip to content

Having redudant data passed to an object makes it so the second reference of the same object is not updated #222

@JeffreyDevloo

Description

@JeffreyDevloo

Problem description

Updating a viewModel with a computed property from propertyA and propertyA at the same time ensures that either one will be fetched from the cache and it won't get updated

function DerivedViewModel(data) {
  var self = this;
  ko.mapping.fromJS(data, {}, self);
}

var mapping = {
  bundlemodels: {
    key: function(data) {
      return ko.utils.unwrapObservable(data.id)
    },
    create: function(options) {
      debugger
      return new DerivedViewModel(options.data)
    }
  }
}

function ViewModel(data) {
  var self = this;

  // Bundle is a JS object
  // Say we want to unpack the bundle data to create a list of viewmodels instead of creatng a viewmodel
  data = Object.assign(data, {
    'bundlemodels': self.extractFromBundle(data.bundle || {})
  })
  var vmData = Object.assign({
    bundle: {}
  }, data)
  ko.mapping.fromJS(vmData, mapping, self);
}
ViewModel.prototype = {
  /**
   * Return an Array of data from the bundledata to track all updates in an observableArray
   */
  'extractFromBundle': function(bundleData) {
    return Object.values(bundleData)
  },
  /**
   * Since the Object was created with the bundle extracted, the update flow should do the same
   * The issue here is that the bundle data contains the very same data as the items that would 
   * be extracted from it. This triggers the alreadyMapped line inthe updateViewModel of the ko.mapping
   * causing my mapped item not to be updated
   */
  update: function(data) {
    var self = this;
    data = Object.assign(data, {
      'bundlemodels': self.extractFromBundle(data.bundle || {})
    })
    ko.mapping.fromJS(data, self)
  }
}
var viewModelData = {
  bundle: {
    id1: {
      name: 'Amazing name',
      id: 'id1'
    },
    id2: {
      name: 'Amazing name2',
      id: 'id2'
    }
  }
}
var updatedViewModelData = {
  bundle: {
    id1: {
      name: 'Amazing name',
      id: 'id1'
    },
    id2: {
      name: 'Amazing namechanged',
      id: 'id2'
    }
  }
}
var initialViewModel = new ViewModel(viewModelData)
var updatedViewModel = new ViewModel(viewModelData)
updatedViewModel.update(updatedViewModelData)

Following my explanation:
updatedViewModel.bundle and updatedViewModel.bundlemodels contain the same data. When bundle is checked for updating: this model data is cached.
When updating the bundlemodels, it retrieves the cacheddata and says it no longer has to update it.
This leaves updatedViewModel behind with only one of the two properties being properly updated

Calling the update function:

  • Loops over the bundle object, makes everything observable and caches all these values within the simpleobject
  • Loops over the bundlemodels array, checks if the objects currently exist within the cache (it does, because the same model was applied for the bundle object) and it does not update the bundlemodels
    The reason for the 'computed' of the bundle object is to have an array with models that are persistent (checking on the key function). The bundle object can not be used to achieve this...

It is an issue that I'm currently facing in my code

Workaround:
The only solution for me is to skip making the bundle object observable. Specifying it within the copy array of the mapping solves my caching issue.

Other solution:
If you'd require both properties to be observable, the copy cannot be used. For that, you'd need to derive the other property from the copy of the original one (they won't reference the same instance then)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions