Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add knockout.mapping TypeScript types #12

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

SomaticIT
Copy link

Since knockout 3.5 is now publishing Typescript types in its own repository, I suggest to do the same here in knockout.mapping.

This types are using the knockout embedded types as a base and extend them to add knockout.mapping features.

Linked issue: knockout/knockout#2353

@maskmaster
Copy link

There is an abandoned PR that improves the typing of fromJS. A somewhat refined version of that is:

type KnockoutObservableType<T> = {
	[P in keyof T]:
	T[P] extends KnockoutObservable<(infer E)> | KnockoutObservableArray<(infer E)> ? T[P] :
	T[P] extends string | boolean | number | Date ? KnockoutObservable<T[P]> :
	T[P] extends Array<(infer E)> ? KnockoutObservableArray<KnockoutObservableType<E>> :
	T[P] extends Function ? T[P] :
	T[P] extends object ? KnockoutObservableType<T[P]> :
	T[P];
};

With the corresponding fromJS declarations:

    fromJS<T>(jsObject: T[]): KnockoutObservableArray<KnockoutObservableType<T>>;
    fromJS<T>(jsObject: T[], targetOrOptions: any): KnockoutObservableArray<KnockoutObservableType<T>>;
    fromJS<T>(jsObject: T[], inputOptions: any, target: any): KnockoutObservableArray<KnockoutObservableType<T>>;
    fromJS<T>(jsObject: T): KnockoutObservableType<T>;
    fromJS<T>(jsObject: T, targetOrOptions: any): KnockoutObservableType<T>;
    fromJS<T>(jsObject: T, inputOptions: any, target: any): KnockoutObservableType<T>;

This really increases the value of having the knockout.mapping typed!

@SomaticIT
Copy link
Author

I can improve my type MappedObservable using your improved version which is very good but I think that we can't use this type as the return type of the fromJS function because the format of the returned value depends on the MappingOptions (with ignore, copy, observerve, etc.).

The good (and always valid) way to implement this in code should be:

// Base Type
interface MyBaseType {
  prop1: string;
  prop2: Date;
}

const baseValue: MyBaseType = {
  prop1: "value 1",
  prop2: new Date()
};

// Default options MappedType
type MyDefaultMappedType = ko.mapping.MappedObservable<MyBaseType>;

const defaultMappedValue = ko.mapping.fromJS<MyDefaultMappedType>(baseValue);

// Custom options MappedType
interface MyCustomMappedType {
  prop1: ko.Observable<string>;
  prop2: Date;
}

const customMappedValue = ko.mapping.fromJS<MyCustomMappedType>(baseValue, { copy: ["Date"] });

In this case, the MappedObservable type is only an helper to quickly generate a default mapped type from a given type.

What do you think?

@maskmaster
Copy link

You're correct, the MappingOptions throws a wrench in the machinery.

However, I would like to be able to call fromJS like this

const autoMappedType = ko.mapping.fromJS({ a: 10, b: ko.observable(20) });
const alsoAutoMappedType = ko.mapping.fromJS({ a: 10, b: ko.observable(20) }, {});

and get the type auto-deduced when not using any mapping options.

@SomaticIT
Copy link
Author

You're correct!
I updated types to match this behavior.

@phinett
Copy link

phinett commented Nov 9, 2020

Will this be merged at some point? Thank you

@SomaticIT
Copy link
Author

@crissdev, any news?

@richardlawley
Copy link

One case I use regularly is mapping a top-level array into an observable array of objects - this uses create at the top level of MappingOptions, which cannot be done with these types. This isn't explicitly documented but has always worked, example code below:

class Person {
    constructor(data: any) {
        ko.mapping.fromJS(data, {}, this);
    }
    Forename = ko.observable<string>(null);
    Surname = ko.observable<string>(null);
    DisplayName = ko.pureComputed(() => `${this.Forename()} ${this.Surname()}`);
}
const input = [
    { Forename: 'Alice', Surname: 'Woods' },
    { Forename: 'John', Surname: 'Smith' }
];
var options = {
    create: (options: any) => new Person(options.data)
};
var output = ko.mapping.fromJS(input, options);

To make this work with your typings, I removed MappingOptionsProperty and moved the create, update and key properties to MappingOptionsBase.

@aimerie
Copy link

aimerie commented Apr 26, 2024

Old, but still relevant! I hope this is incorporated one day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants