Created
July 8, 2015 20:26
My canonical directive structure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference path="../typings/angularjs/angular.d.ts" /> | |
// A canonical layout for defining angular directives in TypeScript | |
module ModuleForDirective { | |
// Defines what we expect to find in $scope. This mainly the stuff | |
// mapped in the scope: attribute of the directive, but it could | |
// include stuff inherited into $scope as well... | |
interface Scope extends ng.IScope { | |
// Stuff defined in { scope: { ... } } (attrs on directive) | |
// Stuff inherited in $scope | |
} | |
class Controller { | |
// View properties (stuff we compute from $scope) | |
// ... | |
static $inject = ["$scope", ...]; | |
constructor(private $scope: Scope, ...) { | |
// Assign watches on stuff in { scope: { ... } } | |
$scope.$watch('prop1', (nv: TypeOfProp1) => { | |
this.computeView(nv, $scope.prop2) | |
}, true); | |
$scope.$watch('prop2', (nv: TypeOfProp2) => { | |
this.computeView($scope.prop1, nv) | |
}, true); | |
} | |
// Any methods you want to be able to invoke on the controller (from the template) | |
// (generally, these are to manipulate values on $scope are to evaluate | |
// computed properties. | |
// This method is used to recompute view properties based on values in $scope | |
// This assumes all members of the Controller class depend on all members in $scope. | |
// If they don't, then multiple functions can be defined to more efficiently compute | |
// subsets based on changes (dependencies) in $scope. | |
private computeView(prop1: TypeOfProp1, prop2: TypeOfProp2) { | |
// Update setup related view quantities | |
// (compute values of members of this class based on p1 and p2 from $scope) | |
// this._____ = f(prop1, prop2); | |
} | |
} | |
export function Directive(): ng.IDirective { | |
return { | |
restrict: "E", // Can only be an Element | |
replace: false, | |
controller: Controller, | |
controllerAs: "cname", | |
template: ..., | |
scope: { | |
prop1: '=', | |
prop2: '=', | |
... | |
// Should match inputs to Scope interface | |
}, | |
// Fancy stuff often requires a link: field to be defined as well | |
}; | |
} | |
Directive.$inject = []; // Any dependencies to inject in call to Directive | |
} | |
angular.module("ModuleName").directive(ModuleForDirective.Directive) |
I don't see anything bad here. Here's what we have at Picnic:
Filename fooDirective.ts
import {dustApp} from "../main";
export class FooDirectiveController {
static $inject = ['$element', '$scope'];
constructor(public $element: JQuery, public $scope: FooDirectiveScope) {
$scope.vm = this;
// Any Jquery access goes here. Use $element
// Setup any $watch on $scope that you need
}
}
export interface FooDirectiveScope extends ng.IScope {
bar: string;
// Local design only
vm: FooDirectiveController;
}
dustApp.directives.directive('foo', function (): ng.IDirective {
return {
restrict: 'E',
scope: {
// NOTE : see documentation in type information
bar: '='
},
templateUrl: 'fooDirective.html',
controller: FooDirectiveController
};
});
Filename fooDirective.html
<div>
Foo
</div>
Note: I'm using external modules
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that
ModuleForDirective.Scope
andModuleForDirective.Controller
are not exported (nor do they need to be). The namesprop1
andprop2
are just examples of attributes the directive might have.I'm using
controllerAs
to cleanly delineate between information that is passed into the directive (members ofScope
) and information that is computed by the directive (members ofController
).It seems like the use of
watch
here is probably not the most efficient. This example assumes the worst case which is thatprop1
andprop2
are objects and that they can potentially change during digests. Obviously, this can be optimized for more specific cases.