1

When creating a templated directive in Angular using TypeScript your scope interface not only represents what is coming into the directive, it also ends up representing what you need to bind to the view.

For instance, given the following HTML.

<my-widget my-title="vm.title"></my-widget>

The TypeScript directive might look like this.

module widgets {
    'use strict';

    export interface IWidgetScope extends ng.IScope {
        myTitle: string;
    }

    export class Widget implements ng.IDirective {

        public templateUrl = 'app/widgets/widget.html';
        public restrict = 'E';
        public scope: {[key: string] : string} = {
            'myTitle': '=',
        };

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

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

        constructor() {
            this.link = (scope: IWidgetScope,
                         element: ng.IAugmentedJQuery,
                         attrs: ng.IAttributes) => {
                var init = () => {
                    scope.handleAction = () => {
                        scope.myTitle='clicked';
                    };
                    scope.$apply();
                };
                element.ready(init);
            }
        }

    }
}

In widget.html you would have something like this.

<div ng-click="handleAction">
    <span>{{title}}</span>
</div>

The problem here is that the compiler will complain that handleAction is not a member of the IWidgetScope interface.

2339 Property 'handleAction' does not exist on type 'IWidgetScope'.

What is the recommended way of handling this situation? It seems like the problem is that the IWidgetScope is being forced to serve both sides external and internal.

Is this possible, or is any preferred?

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
jedatu
  • 4,053
  • 6
  • 48
  • 60

1 Answers1

4

I am using this approach to define a directive:

module widgets {
    'use strict';

    export interface IWidgetScope extends ng.IScope {
        myTitle: string;
    }

    export class Widget implements ng.IDirective {

        public templateUrl = 'app/widgets/widget.html';
        public restrict = 'E';
        public scope: {[key: string] : string} = {
            'myTitle': '=',
        };

        public controller = MyWidgetController;
        public controllerAs: string = "Ctrl";

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

        static factory(): any {
            /* @ngInject */
            var directive = () => {
                return new Widget();
            };
            return directive;
        }
    }
    export class MyWidgetController {
        static $inject = ["$scope", "$element", "$attr"];

        constructor(protected scope: IWidgetScope,
                   protected element : ng.IAugmentedJQuery,
                   protected attr : ng.IAttributes)
                   {
                   }

        public handleAction () {            
            this.scope.myTitle='clicked';            
        }

    }
}

And in template I can access controller methods with Ctrl. syntax

<div ng-click="Ctrl.handleAction()"> // this is from scope.Ctrl
    <span>{{title}}</span> // this is from scope
</div>

The TS code in a playground

There is a link to Q & A (and other links) with explanation how to create controller, directive... and also working plunkers:

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • This makes sense. It supports the Controller-As technique and helps to better visualize the difference between the isolate scope mapping and what internal methods the directive may have. – jedatu Nov 25 '15 at 06:22
  • Great to see that it could help a bit. Enjoy Typescript with Angular... good option... when Angular2 is coming... ;) – Radim Köhler Nov 25 '15 at 06:23
  • I am discovering that another benefit of this controller approach is the new `bindToController` feature which allows you to attach incoming directive data to the controller instead of scope. – jedatu Dec 05 '15 at 15:38
  • 1
    Exactly. That way you are starting in fact - to think in angular 2 ;) – Radim Köhler Dec 05 '15 at 15:39
  • This is what I am looking for. Thanks so much. – Thomas.Benz Nov 26 '16 at 15:38