Search code examples
knockout.jstypescriptknockout-3.0typescript1.6definitelytyped

KnockoutJS & TypeScript (DefinitelyTyped): Adding Properties to Observables


In KnockoutJS, you can add additional properties to your observableArrays such as:

class Table {

    items: KnockoutObservableArray<SomeType>;

    constructor() {
        this.items = ko.observableArray<SomeType>();
        this.items.someMethod = ko.pureComputed([...]);
    }
}

That said, TypeScript will mark the someMethod property as an error and won't compile.

It appears that the above works for KnockoutObservable<T>, but not for KnockoutObservableArray<T> (using DefinitelyTyped's Knockout definition files).

Is there a way to allow these additional properties without having to resort to the following for every single one?

/// Inside a custom definition file
interface KnockoutObservableArray<T> {
    someMethod: any; // Works, but is tedious and pollutes the definitions
    [x: string]: any; // Indexers don't work...
}

I am also not keen on using any for the parent property's definition.


Edit

Okay, it appears that, in order to get this to work, one needs to use the indexer option and then reference the dynamic property as this.items['someMethod']() rather than this.items.someMethod(). It looks like the TypeScript spec simply doesn't allow for dynamic or arbitrary properties in class definitions.


Solution

  • If you're going to attach arbitrary properties to typed objects, the easiest way I've found to do it is to cast the object to any when you want to read and write those properties. So in your case you'd do:

     (self.items as any).someMethod = ko.pureComputed();
     var value = (self.items as any).someMethod();
    

    This syntax is fairly clean and lets you access arbitrary properties, without losing the general type protection elsewhere.


    That being said, I'd definitely recommend against this pattern. Adding arbitrary properties to library objects like this just makes the code a bit more difficult to read and reason about and doesn't really provide any benefit. Assuming that someMethod is some logic specific to the items, I would either attach the someMethod logic to the parent viewmodel itself, or if there is enough logic related to items, pull that logic into its own viewmodel containing both the list of items and related logic.