sunny-mittal
sunny-mittal

Reputation: 509

Knockout components using OOP and inheritance

I was hoping I could get some input on how to use Knockout components in an object-oriented fashion using Object.create (or equivalent). I'm also using Postbox and Lodash, in case some of my code seems confusing. I've currently built a bunch of components and would like to refactor them to reduce code redundancy. My components, so far, are just UI elements. I have custom input boxes and such. My initial approach was as follows, with some discretion taken to simplify the code and not get me fired :)

// Component.js
function Component() {
  var self = this
  self.value = ko.observable()
  self.initial = ko.observable()
  ...
  self.value.subscribeTo('revert', function() {
    console.log('value reverted')
    self.value(self.initial())
  }
}

module.exports = Component

// InputBox.js
var Component = require('./Component')
var _ = require('lodash')

function InputBox(params) {
  var self = this
  _.merge(self, params) // quick way to attach passed in params to 'self'
...
}

InputBox.prototype = Object.create(new Component)

ko.components.register('input-box', InputBox)

Now this kind of works, but the issue I'm having is that when I use the InputBox in my HTML, I pass in the current value as a parameter (and it's also an observable because the value is retrieved from the server and passed down through several parent components before getting to the InputBox component). Then Lodash merges the params object with self, which already has a value observable, so that gets overwritten, as expected. The interesting part for me is that when I use postbox to broadcast the 'revert' event, the console.log fires, so the event subscription is still there, but the value doesn't revert. When I do this in the revert callback, console.log(self.value(), self.initial()), I get undefined. So somehow, passing in the value observable as a parameter to the InputBox viewmodel causes something to go haywire. When the page initially loads, the input box has the value retrieved from the server, so the value observable isn't completely broken, but changing the input field and then hitting cancel to revert it doesn't revert it.

I don't know if this makes much sense, but if it does and someone can help, I'd really appreciate it! And if I can provide more information, please let me know. Thanks!

Upvotes: 1

Views: 860

Answers (2)

sunny-mittal
sunny-mittal

Reputation: 509

To everyone who responded, thank you very much! I've found a solution that works better for me and will share it here in case anyone is interested.

// Component.js (only relevant parts shown)
function Component(params) {
  var self = this
  _.merge(self, params)

  self.value.subscribeTo('some event', function() {
    // do some processing
    return <new value for self.value>
}
module.exports = Component

// InputBox.js
var Component = require('./component')

function InputBox(params) {
  var self = this
  Component.call(self, params)
}

By taking this approach, I avoid the headache of using prototypes and worrying about the prototype chain since everything Component does is done directly to the "inheriting" class. Hope this helps someone else!

Upvotes: 0

Roy J
Roy J

Reputation: 43899

JavaScript does not do classical inheritance like C++ and such. Prototypes are not superclasses. In particular, properties of prototypes are more like static class properties than instance properties: they are shared by all instances. It is usual in JS to have prototypes that only contain methods.

There are some libraries that overlay a classical-inheritance structure onto JavaScript. They usually use "extends" to create subclasses. I don't use them, so I can't recommmend any in particular, but you might look at Coffeescript if you like the classical-inheritance pattern.

I often hear "favor composition over inheritance," but I generally see a lot of emphasis on inheritance. As an alternative, consider Douglas Crockford's "class-free object-oriented programming", which does away with inheritance entirely.

For what you're trying to do here, you probably want to have InputBox initialize itself with Component, something like:

function InputBox(params) {
  var self = this
  Component.bind(self)(); // super()
  _.merge(self, params) // quick way to attach passed in params to 'self'
...
}

The new, merged, value will not have the subscription from Component, because the subscription is particular to Component's instance of the observable, which will have been overwritten.

Upvotes: 2

Related Questions