AngularJS Directive for twitter bootstrap form-group

All we need is an easy explanation of the problem, so here it is.

I’ve been playing with angular lately, so far so good, but im struggling with directives.

I’m trying to create a directive that generates the html mark up for a standard bootstrap form group with its corresponding validation messages.

So basically I’m trying to convert this:

<form-group label="Password">
        <input type="password" data-ng-model="vm.password" name="password" id="password" class="form-control form-control-validate"
               required data-ng-minlength="6"
               data-required-error="Password is required" data-minlength-error="Your password must have at least 6 characters" />

into this:

<div class="form-group" data-ng-class="{'has-error': invalid}">
        <label for="password" class="col-md-2 control-label">Password</label>
        <div class="col-md-10">
            <input data-ng-model="vm.password" type="password" id="password" name="password" class="form-control"
                   required data-ng-minlength="6"/>
            <div data-ng-show="changePasswordForm.$dirty && changePasswordForm.oldPassword.$invalid">
                <label data-ng-show="changePasswordForm.oldPassword.$error.required" class="label label-danger">
                    Password is required
                    <br />
                <label data-ng-show="changePasswordForm.oldPassword.$error.minlength" class="label label-danger">
                    Your password must have at least 6 characters

So far this is what I have:

app.directive('formGroup', function () {
    return {
        templateUrl: 'app/directives/formGroup.html',
        restrict: 'E',
        replace: true,
        transclude: true,
        require: "^form",
        scope: {
            label: "@",
        link: function (scope, element, attrs, formController) {
            var input = element.find(":input");
            var id = input.attr("id");
            scope.for = id;
            var inputName = input.attr("name");
            // Build the scope expression that contains the validation status.
            // e.g. "form.example.$invalid"
            var inputInvalid = [formController.$name, inputName, "$invalid"].join(".");
            scope.$parent.$watch(inputInvalid, function (invalid) {
                scope.invalid = invalid;


<div class="form-group" ng-class="{ 'has-error': invalid }">
   <label class="col-md-2 control-label" for="{{for}}">{{label}}</label>
   <div class="col-md-10">
      <div data-ng-transclude=""></div>

This sets correctly the bootstrap class “has-error” to the form-group if the input is invalid.

Now I want to add validation messages, and I couldn’t find a way that works. This is what I have:

app.directive('formControlValidate', function () {
    return {
        templateUrl: "app/directives/formControlValidate.html",
        restrict: 'C',
        require: ["^form", "ngModel"],
        scope: { },
        transclude: true,
        //replace: true,
        link: function (scope, element, attrs, controls) {
            var form = controls[0];
            var inputName =;
            var inputErrors = [form.$name, inputName, "$error"].join(".");
            scope.$parent.$watch(inputErrors, function (newValue) {
                if (newValue) {
                    scope.errors = [];
                    angular.forEach(newValue, function (value, key) {
                        var error = attrs[key + 'Error'];
                        if (value && error) {
            }, true);


<div class="controls" ng-transclude></div>
    <div data-ng-repeat="error in errors">
    <div class="label label-danger">

But this doesn’t work. I’m randomly changing parameters in both directives but can’t figure out what how to make it work.

Any ideas or improvements would be greatly appreciated.


How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

UPDATE: this is my latest gist (angular 1.3):

OLD solution:

I tried something similiar, maybe this link can help you too:

This was my solution. I don’t know why but I had to use form.$dirty, it was not possible to use input.$dirty..
(and I use angular-translate)


<form-group input="form.password">
  <input type="password" class="form-control" placeholder="{{ 'user.password' | translate }}" required
          name="password" ng-model="user.password" />


  app.directive('formGroup', function ($parse) {
    return {
      restrict: 'E',
      require: '^form', 
      transclude: true,
      replace: true,
      scope: {
        cssClass: '@class',
        input: '='
      template: '<div class="form-group" ng-class="{\'has-error\':hasError, cssClass:true}">'+
                  '<div ng-transclude></div>' +
                  '<div ng-show="hasError">' +
                    '<span ng-repeat="(key,error) in input.$error" class="help-block"' +
                            'ng-show="input.$error[key]">{{\'form.invalid.\'+key | translate}}</span>' +
                  '</div>' +
      link: function (scope, element, attrs, ctrl) {
        var form = ctrl;
        var input = attrs.input;

        scope.$parent.$watch(input+'.$invalid', function (hasError) {
          scope.hasError = hasError && form.$dirty;

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply