Conditioning Knockout Observables: reject values

Wouldn't it be nice if we could restrict the value written to a Knockout observable? Some values might mess up your model completely while others just don't make sense. How would one create a conditioned observable that rejects invalid values? It turns out that conditioning an observable is not so hard.

In this article, I'm using TypeScript, but it shouldn't be too difficult to port it to JavaScript. I'll be sharing the code as a Gist (at the end).

Note: the solution might be too ridged as it flatly rejects all values that do not satisfy the condition.

  1. Intro
  2. A small example: Sudoku
  3. The Cell
  4. Conditioned Observable
    1. Inner workings
    2. Implementation
    3. Conditioning in action
  5. Summary
  6. Comments

A small example: Sudoku

Let's think of a Sudoku-cell. The value of this cell is restricted by a number of possible values: 1-9. It is also limited by the numbers used in the adjacent vertical and horizontal rows (orange) and the box the cell is in (purple).

Not allowed due to row: 1, 4, 7, 8. Not allowed due to box: 2, 3, 6, 8, 9. Allowed: 5.
All the restrictions of a Sudoku visualized:
Not allowed due to row: 1, 4, 7, 8.
Not allowed due to box: 2, 3, 6, 8, 9. Allowed: 5.

The Cell

We can model this Sudoku-cell like a class with options and a value. Options can be removed from the cell. This is neat when an option is no longer possible due to the modification of a related cell. When a value is set, the cell should check if that value is still an option.

class Cell {

  public options = ko.observableArray([1,2,3,4,5,6,7,8,9]);

  public value = ko.observable<number>(null);

}

How can we restrict that number?

Conditioned Observable

Let's extend Knockout with a conditionedObservable<T> that takes an initial value and a condition lambda. First, we'll need to go through some TypeScript-trouble to get everything type-safe.

interface KnockoutStatic {
    conditionedObservable<T>(
        initialValue: T | null, 
        condition: (value:T) => boolean
    ) : KnockoutComputed<T> 
}

Inner workings

The basic idea comes from this blog. Building the Conditional Observable works as follows:

  1. Create a hidden observable that stores the value.
  2. Return a writable computed observable based on the hidden observable.
  3. Validate the value before accepting and storing it.

Implementation

Observe the implementation:

ko.conditionedObservable = function<T>(
    initialValue: T | null, 
    condition: (value: T) => boolean
){    
    let obi = ko.observable(initialValue);
    let computer = ko.computed({
        owner: this,
        read: () => obi(),
        write: (newValue: T) => {

            //unwrap value - just to be sure
            let v = ko.unwrap(newValue);

            //check condition
            if(condition(v)){
                
                //set it to the observable
                obi(v);
            }
            else {
                //reset the value
                computer.notifySubscribers();
            }
        }
    });

    return computer;
};

Looks pretty simple. Yet it is very effective.

Conditioning in action

Let's add it to the Cell-class. The observable is now conditioned to only allow a null-value or values that are still available in the options-array.

class Cell {

  public options = ko.observableArray([1,2,3,4,5,6,7,8,9]);

  public value = ko.conditionedObservable<number>(null, (v) => {        
    return v == null || this.options().indexOf(parseInt(<any>v)) > -1;
  });

}

Notice how lambda expressions (ES6 arrow function) allow the code to use this in an intuitive way.

Summary

Knockout is a versatile MVVM-framework that allows you to build new features onto it. With a few lines of code, you can add constraints to your observables. Conditioning made easy.

The JavaScript for the Conditioned Observable is available as Gist.

expand_less