jedatu
jedatu

Reputation: 4123

AngularJS directive model symbols with TypeScript interface

How do you initialize isolate scope for an AngularJS directive when using a strongly-typed interface? If you want to use a model interface as well as the "@" "=" and "&" binding symbols, it seems you confuse the compiler because you can't assign a string variable to properties with incompatible types.

For instance:

module widgets {
    'use strict';

    export interface IWidget extends ng.IScope {
        removed: boolean;
        onChanged: ()=>void;
        description: string;
        toggle:()=>void;
    }

    export class Widget implements ng.IDirective {

        public templateUrl = 'app/widgets/widget.html';
        public restrict = 'E';
        public scope: IWidget = {
            removed: "@",
            onChanged: "&",
            description: "="
        };

        public link: (scope: IWidget, element: ng.IAugmentedJQuery, 
                      attrs: ng.IAttributes) => void;

        static factory(): any {
            /* @ngInject */
            var directive = () => {
                return new Widget();
            };
            return directive;
        }

        constructor() {
            this.link = (scope: IWidget, element: ng.IAugmentedJQuery, 
                         attrs: ng.IAttributes) => {
                var init = () => {
                    scope.toggle = this._toggle.bind(this);
                    scope.$on('$destroy', this.destruct);
                    scope.$apply();
                };
                element.ready(init);
            };
        }

        private _toggle() {
            // updated: this throws the type error
            this.scope.removed = !this.scope.removed;
        }

        private destruct() {    
        }
    }
}

Given the above code, notice that onChanged will produce compiler errors, because you can't assign a string "&" to a function.

You may get the following error: 2349 Cannot invoke an expression whose type lacks a call signature.

Or this error on the removed property: 2322 Type 'boolean' is not assignable to type 'string'.

In fact, even if you use any for the model the compiler still wont let you change the underlying type of a property after it is defined.

Is there any way to deal with this?

Upvotes: 2

Views: 297

Answers (2)

Radim Köhler
Radim Köhler

Reputation: 123901

The issue here is the directive definition.

It expects scope to be kind of IDictionary<string, string>, but type any would work as well:

export class Widget implements ng.IDirective {

    public templateUrl = 'app/widgets/widget.html';
    public restrict = 'E';
    //public scope: IWidget = {
    public scope: any = {
        removed: "@",
        onChanged: "&",
        description: "="
    };

The scope here, in the directive definition, is about describing how to handle html attributes (passed as a value, or as function ...) . It does not have type IWidget. That type IWidget will come into play when the scope instance is passed to controller or link

As said above, the IDictionary will work as well:

public scope: {[key: string] : string} = {
//public scope: any = {
    "removed": "@",
    "onChanged": "&",
    "description": "="
};

But the any : {...} and simplified notation will do the same.

Upvotes: 1

basarat
basarat

Reputation: 276343

You may get the following error: 2349 Cannot invoke an expression whose type lacks a call signature.

The main cause is that scope members need to be stringly linked (and one of the reason I am no longer an angular advocate).

The member onChanged should be callable just fine e.g. :

    public scope: IWidget = {
        removed: "@",
        onChanged: "&",
        description: "="
    };

    public link: (scope: IWidget, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void {
            scope.onChanged(); // WILL WORK FINE
    }

You are probably misusing the annotation IWidget in your code (not provided with the question).

Upvotes: 2

Related Questions