5

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" />
</form-group>

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>
                <label data-ng-show="changePasswordForm.oldPassword.$error.minlength" class="label label-danger">
                    Your password must have at least 6 characters
                </label>
            </div>
        </div>
</div>

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;
            });
        }
    };
});

formGroup.html:

<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>
   </div>
</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 = attrs.name;
            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) {
                            scope.errors.push(error);
                        }
                    });
                }
            }, true);
        }
    };

formControlValidate.html:

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

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.

Thanks!

1
  • Could you put this into Plunkr or something? Commented Mar 24, 2014 at 18:46

1 Answer 1

9

UPDATE: this is my latest gist (angular 1.3): https://gist.github.com/lpsBetty/3259e966947809465cbe


OLD solution:

I tried something similiar, maybe this link can help you too: http://kazimanzurrashid.com/posts/create-angularjs-directives-to-remove-duplicate-codes-in-form

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)

HTML:

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

Directive:

  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>' +
                '</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;
        });
      }
    };
  });
Sign up to request clarification or add additional context in comments.

8 Comments

FWIW I was able to solve the $dirty issue by changing the callback function to: function(hasError, _, parentScope) { scope.hasError = hasError && parentScope.$eval(attrs.input).$dirty; } There might be an easier way, however.
I've created a gist for angular 1.3: gist.github.com/lpsBetty/3259e966947809465cbe
How would we handle clearing the form when it is submitted? I added a hook when submitting form to set the form to pristine with $.setPristine() but hasError would still be true
I've created a service for 'cleaning' the form: angular.forEach(form, function(input) { if(!input) { return; } angular.forEach(input.$error, function(invalid, errorKey) { if(invalid) { input.$setValidity(errorKey, true); } }); });
not too familiar with services, could you provide examples on how its implemented?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.