Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement ng.IDirectiveFactory in TypeScript

I recently updated my angular.d.ts file in my typescript project. I am now getting a typescript compile error in my Directive definitions. I noticed the following in the updated angular.d.ts file:

interface IDirectiveFactory {
    (...args: any[]): IDirective;
}

I am trying to figure out how to implement this interface.

I get this compiler error: Type ng.DirectiveFactory requires a call signature, but type "MyDirective" lacks ones.

This is how my directive looks right now (which used to work fine with older angular.d.ts file):

class MyDirective{
    constructor() {
        var directive: ng.IDirective = <ng.IDirective>{};
        directive.priority = 0;
        directive.restrict = "E";
        directive.scope = {...};
        directive.template = '<div></div>';
        directive.link = {...}
        return directive;
    }
}

And this is where I register the MyDirective class with angular:

angular.module("MyModule", [])
        .directive('myDirective', MyDirective);

The above compiler error makes perfect sense, but how do I implement the (...args: any[]): IDirective signature) ?

Thanks in advance

like image 978
ZeroOne Avatar asked Aug 09 '14 05:08

ZeroOne


3 Answers

I know this is an old question, but I came across this problem as well and thought I would share my solution:

class MyDirective implements ng.IDirective {
    priority = 0;
    restrict = 'E';
    scope = {...};
    template = '<div></div>';

    link(scope: ng.IScope
        , element: ng.IAugmentedJQuery
        , attributes: IAttributes
        , controller: any
        , transclude: ng.ITranscludeFunction) {
        ...
    }
}

angular.module("MyModule", [])
    .directive('myDirective', () => new MyDirective());

I like this solution because it allows you to use the full benefits of a TypeScript class.

UPDATE If you want to use this approach to simplify your link function using private class functions or fields, you will need to define your link function slightly differently:

class MyDirective implements ng.IDirective {
    priority = 0;
    restrict = 'E';
    scope = {...};
    template = '<div></div>';

    link = (scope: ng.IScope
        , element: ng.IAugmentedJQuery
        , attributes: IAttributes
        , controller: any
        , transclude: ng.ITranscludeFunction) => {
        ...
    }
}

angular.module("MyModule", [])
    .directive('myDirective', () => new MyDirective());

(note that the link method is here declared as a fat arrow function rather than a class function)

This is because when Angular wires this up, it does so in a way that doesn't preserve the this reference for the class. By defining it with a fat arrow function, the compiled JavaScript will define the function in a way that will preserve the this reference. Otherwise you will get lots of errors trying to run the code.

like image 125
Joe Skeen Avatar answered Nov 18 '22 11:11

Joe Skeen


The old signature of directive() used to be...

directive(name: string, directiveFactory: Function): IModule;

It is legal for a class to be used as a Function. But this commit changed the signature to:

directive(name: string, directiveFactory: IDirectiveFactory): IModule;

IDirectiveFactory is a function that returns an IDirective, so directive() no longer accepts a class for the directiveFactory argument. Change it to...

function MyDirective () : ng.IDirective {
    var directive: ng.IDirective = <ng.IDirective>{};
    directive.priority = 0;
    directive.restrict = "E";
    directive.scope = {};
    directive.template = '<div></div>';
    return directive;
}

angular.module("MyModule", [])
    .directive('myDirective', MyDirective);
like image 43
Anthony Chu Avatar answered Nov 18 '22 12:11

Anthony Chu


Joe Skeen supplied an answer which was perfectly in line of what I was going for.

There is another guy Aaron Holmes worked out a similar solution to his, which includes some improvements to handling link.

http://blog.aaronholmes.net/writing-angularjs-directives-as-typescript-classes/#comment-2111298002

As opposed to Joe's answer above, instead of referencing ng.IScope directly, if you use an interface, you can define your own scope variables in the TypeScript fashion (intellisense!)

Also moving the logic for link to the constructor seems like a better way of doing it.

link = (scope: ng.IScope
    , element: ng.IAugmentedJQuery
    , attributes: IAttributes
    , controller: any
    , transclude: ng.ITranscludeFunction) => {
    ...
}

to:

link = (scope: IMyScope
    , element: ng.IAugmentedJQuery
    , attributes: IAttributes
    , controller: any
    , transclude: ng.ITranscludeFunction) => {
    ...
}

Here is Aaron's complete example:

module MyModule.Directives  
{
    export interface IMyScope: ng.IScope
    {
        name: string;
    }

    export class MyDirective
    {
        public link: (scope: IMyScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void;
        public template = '<div>{{name}}</div>';
        public scope = {};

        constructor()
        {
            MyDirective.prototype.link = (scope: IMyScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) =>
            {
                scope.name = 'Aaron';
            };
        }

        public static Factory()
        {
            var directive = () =>
            {
                return new MyDirective();
            };

            directive['$inject'] = [''];

            return directive;
        }
    }
}

One pitfall: You need to use a colon ":" instead of a "=" when declaring link if you're moving the link logic to the constructor as above.

like image 2
William S Avatar answered Nov 18 '22 13:11

William S