Wrapping localStorage in a writeable computed obse

2019-06-27 11:31发布

问题:

I am new to KnockoutJS and am curious to see if this is possible. I am trying to wrap a local storage object in a writeable computed so that I can take advantage of the auto-binding goodness of KnockoutJS. However, the "read" operation doesn't reference any observables - consequently the initial value never gets updated:

<select data-bind="foreach: logLevelsArray, value: currentLogLevel">
    <option data-bind="attr: { value: $index() }, text: $data"></option>
</select>

_.extend(DevUtilitiesViewModel.prototype, {
    ...
    logLevelsArray: ['error', 'warning', 'info', 'debug'],
    currentLogLevel: ko.computed({
        read: function() {
            return localStorage.getItem("logger-level");
        },
        write: function( newValue ) {
            localStorage.setItem("logger-level", newValue);
        }
    })
    ...
});

DevUtilitiesViewModel.currentLogLevel(); // 2 (default)
DevUtilitiesViewModel.currentLogLevel(4);
localStorage.getItem("logger-level"); // 4 - write was successful
DevUtilitiesViewModel.currentLogLevel(); // 2 - still the original value

I understand that this is the expected behavior and I understand why. I also understand that I can make currentLogLevel a simple observable and subscribe to it and update the local storage that way. But then I have to keep track of the subscription and dispose of it manually, write more code, and so forth. I'm just trying to see if there is a way to do what I am trying to do: provide an observable getter/setter for local storage.

回答1:

You need to come up with a scheme to be notified of any changes to local storage so you can have a way to depend on them.

Decorate the localStorage.setItem() function (and optionally the localStorage.removeItem() function) to notify on any changes. Also listen to the storage event for changes coming from other open tabs.

With this, we'll need to register a dependency on your observable. Looks like the only way is to use an observable as a source of notifications and call it. You can wrap this logic up in a localStorageObservable.

(function () {
    var localStorageObserver = ko.observable();

    function notifier(fn) {
        return function () {
            fn.apply(this, arguments);
            localStorageObserver.notifySubscribers(arguments[0]);
        };
    }
    localStorage.setItem = notifier(localStorage.setItem);
    localStorage.removeItem = notifier(localStorage.removeItem);
    window.addEventListener('storage', function (event) {
        localStorageObserver.notifySubscribers(event.key);
    }, false);
    // not sure how to capture changes in the form:
    //     localStorage.property = value;

    ko.localStorageObservable = function (key) {
        var target = ko.dependentObservable({
            read: function () {
                localStorageObserver(); // register on any changes
                return localStorage.getItem(key);
            },
            write: function (value) {
                localStorage.setItem(key, value);
            }
        });
        target.key = key;
        return target;
    };
}());

With this, you can now sync up with local storage.

_.extend(DevUtilitiesViewModel.prototype, {
    ...
    logLevelsArray: ['error', 'warning', 'info', 'debug'],
    currentLogLevel: ko.localStorageObservable('logger-level'),
    ...
});


回答2:

If you only want to detect when you use your writable computable's write method, you can use a observable to handle the notification pretty easily:

currentLogLevel: ko.computed({
    owner: ko.observable(localStorage.getItem("logger-level")),
    read: function() { return this() },
    write: function( newValue ) {
        localStorage.setItem("logger-level", newValue);
        this( newValue );
    }
})

Bear in mind though that this won't detect any exterior modification of localStorage