Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing angularjs directives as classes in Typescript

So after taking a look at some of the examples of angularjs directives in typescript, it seems most people agree to use functions instead of classes when implementing them.

I would prefer to have them as a class and attempted to implement them as follows:

module directives
{    
    export class search implements ng.IDirective
    {        
        public restrict: string;
        public templateUrl: string;

        constructor()
        {            
            this.restrict = 'AE';
            this.templateUrl = 'directives/search.html';
        }

        public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
        {
            element.text("Hello world");

        }
    }
} 

Now this works fine. However, I need to have an isolated scope with some attributes and I'm struggling to find out how to include that in the class itself.

logic dictates that since I can have

public restrict: string;
public templateUrl: string;

I should be able to have something like:

public scope;

But I'm not sure if this is correct or how to carry on from there (i.e how to add the attributes to the scope).

Anybody know how to solve this? (hopefully, without having to revert to a function if possible)

Thanks,

like image 267
Walter Avatar asked May 08 '14 07:05

Walter


3 Answers

Creating directives as classes can be problematic since you still need to involve a factory function to wrap its instantiation. For example:

export class SomeDirective implements ng.IDirective {
    public link = () => {
    }

    constructor() {}
}

What Doesn't Work

myModule.directive('someDirective', SomeDirective);

Since directives are not invoked using 'new' but are just called as factory functions. This will cause problems on what your constructor function actually returns.

What Does (with Caveats)

myModule.directive(() => new SomeDirective());

This works fine provided you don't have any IoC involved, but once you start introducing injectables, you have to maintain duplicate parameter lists for your factory function and your directive contstructor.

export class SomeDirective implements ng.IDirective {
    ...
    constructor(someService: any) {}
}

myModule.directive('someDirective', ['someService', (someService) => new SomeDirective(someService)]);

Still an option if that is what you prefer, but is important to understand how the directive registration is actually consumed.

An alternative approach

The thing that is actually expected by angular is a directive factory function, so something like:

export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
   return {
     link: () => {...}
   };
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations

myModule.directive('someDirective', SomeDirectiveFactory);

This has the benefit of allowing the use of $inject annotations since angular needs it to be on the factory function in this case.

You could always return an instance of your class from the factory function as well:

export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
   return new SomeDirective(someService);
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations

But really depends on your use case, how much duplication of parameter lists you are okay with, etc.

like image 141
bingles Avatar answered Oct 03 '22 21:10

bingles


Assuming that what you have works without an islolated scope, the following should work with an isolated scope:

module directives
{    
    export class search implements ng.IDirective
    {        
        public restrict = 'AE';
        public templateUrl = 'directives/search.html';
        public scope = {
            foo:'=',
            bar:'@',
            bas:'&'
        };


        public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
        {
            element.text("Hello world");
        }
    }
}
like image 40
basarat Avatar answered Oct 03 '22 23:10

basarat


Here is my proposal:

Directive:

import {directive} from '../../decorators/directive';

@directive('$location', '$rootScope')
export class StoryBoxDirective implements ng.IDirective {

  public templateUrl:string = 'src/module/story/view/story-box.html';
  public restrict:string = 'EA';
  public scope:Object = {
    story: '='
  };

  public link:Function = (scope:ng.IScope, element:ng.IAugmentedJQuery, attrs:ng.IAttributes):void => {
    // console.info(scope, element, attrs, this.$location);
    scope.$watch('test', () => {
      return null;
    });
  };

  constructor(private $location:ng.ILocationService, private $rootScope:ng.IScope) {
    // console.log('Dependency injection', $location, $rootScope);
  }

}

Module (registers directive...):

import {App} from '../../App';
import {StoryBoxDirective} from './../story/StoryBoxDirective';
import {StoryService} from './../story/StoryService';

const module:ng.IModule = App.module('app.story', []);

module.service('storyService', StoryService);
module.directive('storyBox', <any>StoryBoxDirective);

Decorator (adds inject and produce directive object):

export function directive(...values:string[]):any {
  return (target:Function) => {
    const directive:Function = (...args:any[]):Object => {
      return ((classConstructor:Function, args:any[], ctor:any):Object => {
        ctor.prototype = classConstructor.prototype;
        const child:Object = new ctor;
        const result:Object = classConstructor.apply(child, args);
        return typeof result === 'object' ? result : child;
      })(target, args, () => {
        return null;
      });
    };
    directive.$inject = values;
    return directive;
  };
}

I thinking about moving module.directive(...), module.service(...) to classes files e.g. StoryBoxDirective.ts but didn't make decision and refactor yet ;)

You can check full working example here: https://github.com/b091/ts-skeleton

Directive is here: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts

like image 31
b091 Avatar answered Oct 03 '22 23:10

b091