16

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?

2 Answers 2

22

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

Sign up to request clarification or add additional context in comments.

4 Comments

where is this IMyDirectiveScope type defined? and for the app.services.MyFactory what is its definition?
@Oscar you should read the question first: interface IMyDirectiveScope extends ng.IScope
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.
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.
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();
            }
        }
    }
);

3 Comments

Thanks this works, could you give me an example how you would approach this kind of situations without a class?
@user1613512 I edited my answer to include an example without a class
Write unboundLink(...) { var self = this; ... } then you can use self variable inside validate() instead of this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.