Search code examples
javascriptknockout.jscomputed-observable

Does a writable computed observable really need an extra internal observable?


I am trying to use a writable computed observable, but do not really get it why I cannot get some data in the observable by just typing in it. I found that I need an extra observable to copy content from the write: to the read:, which seems weird.

        self.fullName = ko.pureComputed({
            read: function () {
                return ...data from other observables to show in the observable;
            },
            write: function (value) {
                  // value is the content in the input
                  // can be sent to other observables                                       
            },
            owner: self
        });

I found that in the above model, what you type in the observable is not really inside.

In the complete example below (including test output and comments) I use an extra observable to copy data from write: to read:. Crucial in my project is the checkbox to decide if you get two observables filled identically by typing in one of them, or differently by typing in both of them.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Writable computed observables</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script src='https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js'></script>
    </head>
    <body>
        <h1>Writable computed observables</h1>
        <p>See: <a href="http://knockoutjs.com/documentation/computed-writable.html">Knockout Doc</a></p>
        <h2>Original example</h2>
        <div>
            First name: <input data-bind="textInput: firstName" />
            <span data-bind="text: firstName"></span>
        </div>
        <div>
            Last name: <input data-bind="textInput: lastName" />
            <span data-bind="text: lastName"></span>
        </div>
        <div class="heading">
            Hello, <input data-bind="textInput: fullName" />
            <span data-bind="text: fullName"></span>
        </div>
        <h2>My example</h2>
        <div>
            Name: <input data-bind="textInput: Name" />
            <span data-bind="text: Name"></span>
        </div>
         <div>
            Mirror first name? <input type="checkbox" data-bind="checked: cbMirror" />
            <span data-bind="text: cbMirror"></span>
        </div>
        <script>
            function MyViewModel() {
                var self = this;
                // example from knockout site:
                self.firstName = ko.observable('Planet');
                self.lastName = ko.observable('Earth');

                self.fullName = ko.pureComputed({
                    read: function () {
                        //return;
                        return self.firstName() + " " + self.lastName();
                    },
                    write: function (value) {
                          // value is the content in the input field, visible on form,
                          // but apparently not yet in the observable.
                          // now copy this value to first/last-name observables,
                          // that in turn copy it to the read-function,
                          // that returns it to the observable.
                        var lastSpacePos = value.lastIndexOf(" ");
                        if (lastSpacePos > 0) { // Ignore values with no space character
                            self.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
                            self.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
                        }
                    },
                    owner: self
                });

                // checkbox whether or not to mirror between two fields            
                self.cbMirror = ko.observable(false);
                // this observable is to help the writable computed observable to copy input from write() to read()
                self.tmpName = ko.observable();
                // the writable computed observable that may mirror another field, depending on the checkbox
                self.Name = ko.pureComputed({
                        read: function () {
                            return self.cbMirror() ? self.firstName() : self.tmpName();
                        },
                        write: function (value) {
                            //if (self.cbMirror()){
                            //  self.firstName(value);
                            //}else{
                                self.tmpName(value);
                            //}
                        },
                        owner: self
                });
            }

            ko.applyBindings(new MyViewModel());
        </script>
    </body>
    </html>

The question, hence: is there really no better way, to directly get some content from write: to read: without the extra observable self.tmpName?


Update:

With the understanding gained from the Answer below, I could simplify the write: part of my example code, see the unneeded code that I commented-out.


Solution

  • Yes you need an observable if you want to store the user input. A computed doesn't store information it only modifies it. It's like the difference between a variable and a function. Functions don't store their values for later viewing it's only modifying an input and giving an output.

    the writable computed observable doesn't store data. It passes the data to the backing observable. That's the entire point of the write portion is to take the value and store it somewhere. If you add another span to look at the value of tmpName you'll see that it's storing whatever you type unless cbMirror is checked.