Parameterized AngularJS directives

One of the few redeeming facets of JavaScript is that occasionally its dynamic nature comes in very handy. Imagine you’re building a new app with Angular and you just want to get started with simple CRUD functionality. You want to your all “entity views” to look and feel the same internally.

One of the options is, you build a shared entity-view directive and end up with something like this:

1
2
<entity-view entity="myPerson" template="person.html"></entity-view>
<entity-view entity="myProject" template="project.html"></entity-view>

This works. But there’s a funnier option.

What we’d like to have instead is something like this:

1
2
<person-view person="myPerson"></person-view>
<project-view project="myProject"></project-view>

If we try to code both and then compare the results, it will be clear that the differences between them are minimal:

1
2
3
4
5
6
7
8
9
directive('personView or projectView', function() { // NOTE
return {
restrict: 'E',
scope: {
'person or project': '=' // NOTE
},
template: 'person.html or project.html' // NOTE
};
});

Enter factory method! Perhaps we could design a function that returns a directive definition object based on the input parameters. Let’s give it a try:

1
2
3
4
5
6
7
8
9
10
11
12
function makeDirective(entityName) {
var scope = {};
scope[entityName] = '=';

return function() {
return {
restrict: 'E',
template: entityName + '.html',
scope: scope
};
};
}

Now, calling makeDirective('something') will construct a DDO that defines a directive with template set to something.html and a something local scope property derived from the parent scope:

app.js
1
2
3
4
5
6
7
angular.module('demo', [])
.directive('personView', makeDirective('person'))
.directive('projectView', makeDirective('project'))
.controller('AppController', function($scope) {
scope.myPerson = { name: 'John' };
scope.myProject = { codename: 'Something' };
});

person.html
1
<div class="person">{{person.name}}</div>
project.html
1
<div class="project">{{project.codename}}</div>
app.html
1
2
3
4
<div ng-controller="AppController">
<person-view person="myPerson"></person-view>
<project-view project="myProject"></project-view>
</div>

But wait, where does the makeDirective() come from actually? That’s a good question.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
angular.module('demo', [])
.constant('makeDirective', function(entityName) {
var scope = {};
scope[entityName] = '=';

return function() {
return {
restrict: 'E',
template: entityName + '.html',
scope: scope
};
};
})
.directive('personView', function(makeDirective) {
return makeDirective('person');
})
.directive('projectView', function(makeDirective) {
return makeDirective('project');
});

Sure you can use this approach to parameterize controllers, services, filters and whatever else you can imagine.

The Jasmine/Karma test is here.