Search code examples
javascriptknockout.jsknockout-3.0

Writing C#-like extensions in JavaScript for Knockout Observable


Consider the following code:

if (countriesLookup()) {
    countriesLookup().fill(initialData.Countries);
} else {
    const subscription = countriesLookup.subscribe(function (lookup) {
        lookup.fill(initialData.Countries);
        subscription.dispose();
    });
}

Here I have a ko.observable countriesLookup. If by the moment of data initialization the component itself is not initialized yet (countriesLookup() == undefined), I subscribe to that observable, and when it finally get the component, I perform initialization with initial data and get rid of subscription.

Let's forget for the moment that this approach leads to callback hell etc. etc., and consider possible ways to make this less ugly.

I wanted to write something like C# extensions to observable that would be like that in use:

countriesLookup.initialize(x => x.fill(initialData.Countries));

And the implementation supposes to look like that:

ko.observable.prototype.initialize = function(initializeFunc) {
    const currentValue = ???
    if (currentValue()) {
        initializeFunc(currentValue());
    } else {
        const subscription = currentValue.subscribe(function (lookup) {
            initializeFunc(lookup);
            subscription.dispose();
        });
    }
}

Obviously, that does not work, especially because I am not sure if this is possible to get the current value of observable inside the "extension" method.

I am currently thinking in terms of C# programming, where I am somewhat proficient, and would like very much some advice on how I should write such extension.


Perhaps I should explain my problems a bit more. I am aware that the current value could be retrieved with this inside the method added to prototype, but the problem is in the fact that observable returns a function.

For example this

String.prototype.SayHi = function SayHi() {
    return "Hi " + this + "!";
};
"blah".SayHi();      //return "Hi blah!"

works just fine, but this

ko.observable.prototype.SayHi = function SayHi() {
    return "Hi " + this + "!";
};
var a = ko.observable("blah")
a.SayHi();

fails with the following error:

VM543:1 Uncaught TypeError: a.SayHi is not a function at <anonymous>:1:3

I hope it added some clarification.


Solution

  • If I get your point, your aim is to add a custom function for the observables.
    In that case you should use fn instead of prototype.
    Check out this link: http://knockoutjs.com/documentation/fn.html

    I am not sure if this is possible to get the current value of observable inside the "extension" method

    And you can get the current value of your observable using this method, thus your above example is very close to what you want.

    ko.observable.fn.initialize = function(initializeFunc) {
      const currentValue = this;
      if (currentValue()) {
        initializeFunc(currentValue());
      } else {
        const subscription = currentValue.subscribe(function(lookup) {
          initializeFunc(lookup);
          subscription.dispose();
        });
      }
      return this;
    }
    
    var ViewModel = function() {
      this.data = ko.observable().initialize(x => console.log("Value changed to: " + x));
      this.data("100");
    };
    
    ko.applyBindings(new ViewModel());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

    ================================
    NOTE: There is a discussion on why fn is used instead of prototype and someone pointed in there that it is better to use extenders for some performance reasons.
    Read it here: https://github.com/knockout/knockout/issues/979