4
votes

I'm using Breeze + Typescript + Knockout for a Spa, and I'm facing the following problem: when I create a new entity with EntityManager.createEntity, typescript doesn't let me use the observables that Breeze generates from metadata. Typescript "sees" only the "entityAspect" and the "entityType" properties. I'm using the type definitions of DefinitelyTyped. Any help is greatly appreciated!

4

4 Answers

5
votes

You can create an interface for your type which extends breeze.Entity:

/// <reference path="breeze.d.ts" />
module model {
    export interface ResponsesItem extends breeze.Entity {
        ContentTypeID: string;
        Title: string;
        Description: string;
        EventDate: any;
        /* etc. */
    }
}

You can then cast your objects to this interface whenever you need to work with them in a typed way, such as the result of a query when loading from the server :

     private loadResponses(): void {
         this.dataservice.ListResponses()
            .then((data: { results: breeze.Entity[]; query: breeze.EntityQuery; XHR: XMLHttpRequest; }) => {
                var results: model.ResponsesItem[] = <model.ResponsesItem[]>data.results;
                // Do something with typed results array here.
         }).fail((error) => {
            this.handleDataError(error);
         });
      }
2
votes

Check out my answer at Breeze.js typed entities wherein I hacked T4TS to spit out TS definitions that support Breeze.

1
votes

Looking at : https://github.com/borisyankov/DefinitelyTyped/blob/master/knockout/knockout.d.ts

Functions you see on ko (e.g. observable etc) are the functions defined on KnockoutStatic interface because of this line:

declare var ko: KnockoutStatic;

Here is a snippet of this interface:

interface KnockoutStatic {
    utils: KnockoutUtils;
    memoization: KnockoutMemoization;
    bindingHandlers: KnockoutBindingHandlers;
    virtualElements: KnockoutVirtualElements;
    extenders: KnockoutExtenders;

    applyBindings(viewModel: any, rootNode?: any): void;
    applyBindingsToDescendants(viewModel: any, rootNode: any): void;
    applyBindingsToNode(node: Element, options: any, viewModel: any): void;

    subscribable: KnockoutSubscribableStatic;
    observable: KnockoutObservableStatic;
    computed: KnockoutComputedStatic;
    observableArray: KnockoutObservableArrayStatic;
    .....

So if you want new functions on ko you need to define new functions to the KnockoutStatic interface e.g.:

interface KnockoutStatic {
    yourFunc: KnockoutObservableStatic;
}

etc. Since interfaces are open ended you can declare various parts of the interface definition in multiple files. Hope this helps.

0
votes

Below is a page you can drop in your site to generate typescript interface definitions. The page fetches the breeze metadata then iterates through all of the types and outputs a typescript interface declaration for each type. The output of this page can then be pasted in any typescript file (*.ts) or typescript definition file (*.d.ts). Enclose the results in a module declaration if you want to namespace the interfaces: declare module northwind { ... paste interfaces here... }.

Before using the page you'll need to make one edit: change the entity manager's controller url from "api/northwind" to whatever your breeze controller's url is.

The generated interfaces have a dependency on the Knockout.js typescript definitions which you can grab here: https://github.com/borisyankov/DefinitelyTyped/tree/master/knockout/

Using the northwind example from learn.breezejs.com, the output of this definitions generator page would be something like this:

export interface Employee extends breeze.Entity {
    FirstName: KnockoutObservable<string>;
    LastName: KnockoutObservable<string>;
}

you could then execute a query using breeze and cast the results to an array of employees like this:

var manager = new breeze.EntityManager('api/northwind');

var query = new breeze.EntityQuery()
    .from("Employees");

manager.executeQuery(query).then(data => {
    // ***cast the results to a strongly typed array of Employee***
    var employees = <Employee[]>data.results;
}).fail(e => {
    alert(e);  
});

below is the definitions generator page- add a new html file to your project named "definitions.html", run the project and navigate to the page.

<html>
<head>
    <title>Typescript Definition Generator</title>
    <style>
        code {
            white-space: pre;
        }
    </style>
    <script src="//code.jquery.com/jquery-2.1.0.min.js"></script>
    <script src="//ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/q.js/1.0.0/q.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/breezejs/1.4.4/breeze.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            var entityManager = new breeze.EntityManager('api/northwind');
            entityManager.fetchMetadata()
                .then(function () {
                    var html = '',
                        types = entityManager.metadataStore.getEntityTypes(),
                        type,
                        i,
                        j,
                        property,
                        crlf = String.fromCharCode(13),
                        code = document.createElement('code'),
                        script = document.createElement('script');

                    function getJSType(metadataType) {
                        if (/(Int64)|(Int32)|(Int16)|(Byte)|(Decimal)|(Double)|(Single)|(number)/.test(metadataType))
                            return 'number';
                        else if (/(DateTime)|(DateTimeOffset)|(Time)|(Date)/.test(metadataType))
                            return 'Date';
                        else if (/(Boolean)/i.test(metadataType))
                            return 'boolean';
                        return 'string';
                    }

                    for (i = 0; i < types.length; i++) {
                        // type declaration
                        var type = types[i];
                        html += 'export interface ' + type.shortName;

                        // base type
                        html += ' extends ';
                        if (type.hasOwnProperty('baseEntityType')) {
                            html += type.baseEntityType.shortName;
                        } else {
                            html += 'breeze.Entity';
                        }
                        html += ' {' + crlf;

                        // data properties
                        for (j = 0; j < type.dataProperties.length; j++) {
                            property = type.dataProperties[j];
                            if (type.baseEntityType && type.baseEntityType.dataProperties.filter(function (p) { return p.name === property.name; }).length > 0)
                                continue;
                            html += '    ' + property.name;
                            //if (property.isNullable)
                            //    html += '?';
                            html += ': KnockoutObservable&lt;';
                            html += getJSType(property.dataType.name);
                            html += '&gt;; //' + property.dataType.name + crlf;
                        }

                        // navigation properties
                        for (j = 0; j < type.navigationProperties.length; j++) {
                            property = type.navigationProperties[j];
                            if (type.baseEntityType && type.baseEntityType.navigationProperties.filter(function (p) { return p.name === property.name; }).length > 0)
                                continue;
                            html += '    ' + property.name;
                            //if (property.isNullable)
                            //    html += '?';
                            if (property.isScalar)
                                html += ': KnockoutObservable&lt;';
                            else
                                html += ': KnockoutObservableArray&lt;';
                            html += property.entityType.shortName;
                            html += '&gt;;' + crlf;
                        }

                        html += '}' + crlf + crlf;
                    }

                    code.innerHTML = html;

                    $(code).addClass('prettyprint');

                    document.body.appendChild(code);

                    script.setAttribute('src', '//google-code-prettify.googlecode.com/svn/loader/run_prettify.js');
                    document.body.appendChild(script);
                })
                .fail(function (reason) {
                    alert(reason);
                });
        });
    </script>
</head>
<body>
</body>
</html>