Skip to main content

AngularJS Directive with dynamic template

I came across the need for this recently and it was pretty easy to achieve so this should be a quick one.

In my case I had to build a dynamic form from JSON. An endpoint would send me JSON representing a form with a seemingly random amount of fields - of varying types and also some other conditional behaviour which I will spare from hearing about here.

Heres an example of the kind of thing I might expect to see - simplified a bit (a lot) for sake of example.

[
  {
    type: 'text',
    data: '',
    label: 'Name'
  },
  {
    type: 'email',
    data: '',
    label: 'Email address'
  },
  {
    type: 'select',
    data: '',
    label: 'Animal',
    options: ['Cat','Dog','Fish']
  }
]

There were actually about 5 other types I would be sent too, and with extra functionality - I only tell you this so you don't think this approach is a bit heavy-handed for something as simple as this example.

I decided I wanted to use a single directive for these fields, but seeing as each one needed to be be represented differently I wanted each type to have its own template. The directive is pretty simple:

angular.module('exampleApp').directive('dynamicField', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      fieldData: '='
    },
    link: function(scope, element, attrs) {
      // Function returns the correct template for each field.
      scope.getTemplateUrl = function() {
        var type = scope.fieldData.type || 'text';
        return 'dynamic-fields/dynamic-field-'+type+'.html';
      }
    },
    template: '<div class="dynamic-field" ng-include="getTemplateUrl()"></div>'
  };
});

So assuming I have the fields on the scope of my controller I would loop through them like so:

<form>
  <dynamic-field field-data="fieldData" ng-repeat="fieldData in fields"></dynamic-field>
  <input type="submit" value="Submit" />
</form>

So this provides the fieldData to the directive, which contains the type (any anything else the field requires) and the directive used the correct template. Heres an example of the dynamic-field-text.html template:

<label>{{fieldData.label}}</label>
<input type="text" ng-model="fieldData.data" />

And the dynamic-field-select.html template:

<label>{{fieldData.label}}</label>
<select ng-model="fieldData.data" ng-options="o as o for o in fieldData.options">
  <option value="">-- Pick one --</option>
</select>

Pretty simple huh!