1

I'm attempting to wire up an adapter to enable the markup that ASP.Net MVC emits for client-side validation to work within AngularJS, and I've encountered an interesting snag. If I dynamically add the required attribute via a directive compile function:

var myApp = angular.module('myApp', []).directive('valRequired', function() {
    return {
        compile: function (element) {

            element.attr('required', 'required');

            return function() { /* other custom logic here */ }
        }
    };
});

The select element won't validate as required. It only appears to be a problem when dynamically adding the attribute (jsFiddle).

Clarification: I'd like to use MVC's @Html.TextBoxFor(...) as-is. For a DataAnnotations-based model, the data-val-* attributes it emits contain information on which validations to run and what the error messages should be. I'm not looking for assistance wiring up the error messages, I just need to be able to wire up a directive that tells the input, select, etc. to use the required validation.

5
  • 1
    like ng-required="{expression to test}" ? Commented Sep 5, 2013 at 22:53
  • Not really. ASP.Net MVC emits the data-val-required attribute using things like @Html.TextBoxFor(...) and the like. I'd like to automatically transform them into their angularjs equivalents and add some wiring to plumb the MVC-emitted validation messages into the app as well. Commented Sep 6, 2013 at 1:41
  • Perhaps this will help: stackoverflow.com/questions/4844001/… Also, ng-require can be written as data-ng-require and will still be parsed by angular. Commented Sep 6, 2013 at 2:51
  • If you're trying to get the angular equivalent of the MVC emitted attributes (like data-val-required) you can create your own @Html extension methods like @Html.AngularTextBoxFor(...) and save the client-side performance penalty. I've done this in my app. If you're interested I could write this up further at some point. Commented Sep 6, 2013 at 5:14
  • @jandersen I understand where you're coming from. However, I'd rather not reinvent the wheel; MVC already does an excellent job of marking up the DOM with all the necessary information to do DataAnnotations-based validation. I'm just trying to wire those bits up with angularJS instead of jQuery validation. Making a custom HtmlHelper extension method still doesn't solve the entire problem: I also need to wire up the validation messages so that @Html.ValidationMessageFor(...) works properly as well. Commented Sep 6, 2013 at 13:33

2 Answers 2

1

Let me start out with this isn't pretty, but it works. I tried different ways to get the native directive to work, but to no avail. It looks like by the time this directive executes it is too late.

This will look for your data-val-required attribute and add validation to the element.

It will trigger all of the same things so myForm.mySelect.$valid will still work as well as myForm.mySelect.$error.required

http://jsfiddle.net/TheSharpieOne/knc8p/

var myApp = angular.module('myApp', []).directive('valRequired', function () {
    return {
        require: 'ngModel',
        restrict: 'A',
        link: function (scope, elm, attr, ctrl) {
            if (!ctrl || !attr.valRequired) return;
            attr.required = true; // force truthy in case we are on non input element

            var validator = function (value) {
                if (attr.required && (value == '' || value === false)) {
                    ctrl.$setValidity('required', false);
                    return;
                } else {
                    ctrl.$setValidity('required', true);
                    return value;
                }
            };

            ctrl.$formatters.push(validator);
            ctrl.$parsers.unshift(validator);

            attr.$observe('required', function () {
                validator(ctrl.$viewValue);
            });
        }
    };
});


function MyCtrl($scope, $http) {
    $scope.model = {
        property: ''
    };
}
Sign up to request clarification or add additional context in comments.

1 Comment

This appears to be the only way to make it work, I agree. I did some code surfing in the AngularJS codebase on GitHub. It appears that the bit that hooks into the ngModel controller isn't accessible in a way that can be consumed outside of the input or select directives.
0

UPDATE I've thought of a better way to answer the question. The old answer is below the new one.


You can get a reference to AngularJS' required directive and apply it to your own. Heres a code sample that will do this:

var myApp = angular.module('myApp', []).directive('valRequired', function(requiredDirective) {
    var newDirective = {},
        angularDirective = requiredDirective[0]; //This assumes angular's required directive is the first one

    //Copy over all other properties of the angular directive
    angular.extend(newDirective, angularDirective);

    //Change the name of our directive back to valRequired
    newDirective.name = 'valRequired';

    //Provide our own logic in the linking function
    newDirective.link = function(scope, element, attr, ctrl){
        //Super call
        angularDirective.link.apply(this, arguments);
        if(attr.valRequired === 'required'){
            attr.$set('ngRequired', 'true');
        } else {
            attr.$set('ngRequired', 'false');
        }
    }

    return newDirective;
});

<input data-val-required="required" ng-model="foo" />

OLD ANSWER

Using jQuery's or jQLite's attr() method does not change AngularJs' Attributes object. The Attributes object is what directives use as values for their logic.

You will also need to include the ng-required attribute, although you will not need to bind any angular expressions to it. This question will help you out there: Html5 data-* with asp.net mvc TextboxFor html attributes

The reason for this is we need to force angular to apply the directive to this node. Updating the attribute after the compile phase of a template will not notify angular to apply new directives to the node.

This should work:

var myApp = angular.module('myApp', []).directive('valRequired', function() {
    return {
        priority : 101, //The priority needs to run higher than 100 to get around angularjs' default priority for ngRequired
        link: function (scope, element, attr) {

            if(attr.valRequired === 'true'){
                attr.$set('ngRequired', 'true');
            } else {
                attr.$set('ngRequired', 'false');
            }
        }
    };
});

<input ng-required data-val-required="true" ng-model="foo" />

8 Comments

I was not able to get this to work without manually adding ng-required to the tag, the question / question's comments mentioned this was not an option.
The ng-required simply needs to be there, and will be switched on or off depending on the value of data-val-required. This is easily achievable with TextBoxFor. Updated answer to clarify this point
What does ng-repeat have to do with anything?
the ng-required is needed so that the directive is applied. If ng-required is not there, angular would not know to apply the ngRequired directive to this input
You mean ng-required, then, not ng-repeat? If so, never mind =)
|

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.