Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS TypeScript directive link function

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?

like image 274
user1613512 Avatar asked May 17 '14 12:05

user1613512


2 Answers

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

like image 158
tanguy_k Avatar answered Sep 28 '22 12:09

tanguy_k


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();
            }
        }
    }
);
like image 24
rob Avatar answered Sep 28 '22 11:09

rob