15

I'm trying to create an AngularJS directive using TypeScript. My directive requires 'ngModel' and I'm also using a custom service injected in my directive. My main problem is that my service can't be used inside my link function.

Here is an example of what I'm trying to achieve:

module app.directives {

    export var directiveName: string = "theDirective";

    angular.module("myApp").directive(directiveName, 
           (myFactory: app.services.MyFactory) =>
           {
                return new MyDirective(myFactory);
           });

    export interface IMyDirectiveScope extends ng.IScope {
        ngModel: ng.INgModelController;
    }

    export class MyDirective implements ng.IDirective {

        restrict = "A";
        require = "ngModel";
        scope = {
            ngModel:'='
        }

        constructor(private myFactory: app.services.MyFactory) {

        }


        link(scope: IMyDirectiveScope , elem: JQuery, attributes: ng.IAttributes, ngModel: ng.INgModelController) {
            //this is window here

            elem.bind('blur', (evt: JQueryEventObject) => {  
                 //keyword this is also window here, so yeah bummer indeed
                 validate(); 
            });

            function validate() {
                 //I need to use my factory here, but I can seem to get it.
                 //this is always window and I'm kinda stuck here
            }
        }
    }
}

I can't seem to find some more advanced stuff on this topic. All the examples don't I find don't seem to uses services or a complex link function. Please answer this question with some sort of example. It's trickery that you think.

Update: The fact that 'this' inside my link function is window and not 'MyDirective' doesn't make much sense to me. Any ideas why that would be?

tanguy_k
  • 8,999
  • 4
  • 46
  • 50
user1613512
  • 3,280
  • 6
  • 22
  • 31

2 Answers2

21

Using classes and inherit from ng.IDirective is the way to go with TypeScript.

TypeScript includes support for the fat arrow function () => from EcmaScript 6. It's a shorthand syntax that also changes the way the this keyword works:

class MyDirective implements ng.IDirective {
    restrict = 'A';
    require = 'ngModel';
    scope = {
        ngModel: '='
    }

    constructor(private myFactory: app.services.MyFactory) {
    }

    link = (scope: IMyDirectiveScope, elem: JQuery, attributes: ng.IAttributes, ngModel: ng.INgModelController) => {
        console.log(this); // this points to MyDirective instance instead of Window

        elem.bind('blur', (evt: JQueryEventObject) => {
            console.log(this); // this points to MyDirective instance instead of Window
            this.validate(); 
        });
    }

    validate() {
        console.log(this); // this points to MyDirective instance instead of Window
    }


    static factory(): ng.IDirectiveFactory {
        var directive = (myFactory: app.services.MyFactory) => new MyDirective(myFactory);
        directive.$inject = ['myFactory'];
        return directive;
    }
}

app.directive('mydirective', MyDirective.factory());

You can also rely on the old fashion var self = this; pattern:

class MyDirective implements ng.IDirective {
    restrict = 'A';
    require = 'ngModel';
    scope = {
        ngModel: '='
    }

    constructor(private myFactory: app.services.MyFactory) {
    }

    link = (scope: IMyDirectiveScope, elem: JQuery, attributes: ng.IAttributes, ngModel: ng.INgModelController) => {
        console.log(this); // this points to MyDirective instance instead of Window

        var self = this;

        function validate() {
            console.log(self); // self points to MyDirective instance
        }

        elem.bind('blur', function(evt: JQueryEventObject) {
            console.log(self); // self points to MyDirective instance
            validate(); 
        });
    }
}

Related answer: https://stackoverflow.com/a/29223535/990356

Community
  • 1
  • 1
tanguy_k
  • 8,999
  • 4
  • 46
  • 50
  • where is this IMyDirectiveScope type defined? and for the app.services.MyFactory what is its definition? – Oscar Aug 18 '15 at 03:21
  • @Oscar you should read the question first: `interface IMyDirectiveScope extends ng.IScope` – tanguy_k Aug 18 '15 at 10:03
  • Can we achive this without using link function ? I am using Angular 1.4 and since we will be proting our code to Angular 2.0 and link functions are not supported there, i dont want to write this logic using link function.. So please let me know if it is possible to access the element without the link function. – ATHER May 25 '16 at 22:36
  • 1
    This does not actually work completely. Have you tried adding two of the same directives under one parent controller? Have a look at the scope on your variable, you will notice that the scope for both directives is last directives isolated scope, the behavior is not correct. – sbarnard Dec 04 '16 at 01:39
16

Classes work great for controllers and directive controllers but I don't think I'd use one for the whole directive. But if you want to you'd probably have to do something like this:

export class MyDirective implements ng.IDirective {

    public link;

    restrict = "A";
    require = "ngModel";
    scope = {
        ngModel:'='
    }

    constructor(private myFactory: app.services.MyFactory) {
        this.link = this.unboundLink.bind(this);
    }


    unboundLink(scope: IMyDirectiveScope , elem: JQuery, attributes: ng.IAttributes, ngModel: ng.INgModelController) {
        //Now you should be able to access myFactory
        this.myFactory.doSomething();

        elem.bind('blur', (evt: JQueryEventObject) => {  
             //keyword this is also window here, so yeah bummer indeed
             validate(); 
        });

        function validate() {
             //I need to use my factory here, but I can seem to get it.
             //this is always window and I'm kinda stuck here
        }
    }
}

EDIT: Without a class you could do something like this:

angular.module("myApp").directive("theDirective", 
    function(myFactory: app.services.MyFactory) {
        return {
            restrict: 'A',
            require: 'ngModel',
            scope: {'ngModel': '='},
            link: function(scope: IMyDirectiveScope , elem: JQuery, attributes: ng.IAttributes, ngModel: ng.INgModelController) {
                //You can access myFactory like this.
                myFactory.doSomething();
            }
        }
    }
);
Sandor Drieënhuizen
  • 5,950
  • 4
  • 35
  • 77
rob
  • 16,110
  • 12
  • 63
  • 90
  • Thanks this works, could you give me an example how you would approach this kind of situations without a class? – user1613512 May 17 '14 at 15:21
  • @user1613512 I edited my answer to include an example without a class – rob May 18 '14 at 14:47
  • Write unboundLink(...) { var self = this; ... } then you can use self variable inside validate() instead of this. – tanguy_k Mar 23 '15 at 21:39