Angular ng-required not working with custom directive

Hussein Salman picture Hussein Salman · Mar 27, 2016 · Viewed 7.7k times · Source

I am using Angularjs version 1.5 to validate the inputs in my form.

  • ng-required is used to validate the all input required

However, its not working with a custom directive which renders a combo. The combo retrieves the items based on parameter passed to it named 'listId'. Then, it iterates on the 'lookupItems' using ng-repeat. I guess something is missing, like ngModel. Why and how to implement it?

The combo directive:

app.directive('combo', function($http) {
    return {
        restrict: 'AE',
        template: '<div class="input-group"> <select ng-model="selectedItem">' +
            '<option  ng-repeat="option in lookupItems" value={{option.ListValueID}}>{{option.Translation.Value}}</option></select>' +
            '  {{selectedItem}} </div>',
        replace: true,
        scope: {
            listId: '=',
            defaultItem: '=',
            selectedItem: '='
        },
        controller: function($scope) {
            $http({
                method: 'GET',
                url: '/home/listvalues?listid=' + $scope.listId
            }).then(function(response) {
                $scope.lookupItems = response.data;
            }, function(error) {
                alert(error.data);
            });
        },
        link: function(scope, element, attrs) {}
    };
});

The html view: is iterating over attributes which contains the type of control to render, then its set ng-required to a boolean based on 'attribute.Required' which is true.

<form name="profileAttributesForm" ng-controller="metadataCtrl" class="my-form">
    <div ng-repeat="a in attributes">
        <div ng-if="a.DataType == 1">
            <input type="text" name="attribute_{{$index}}" ng-model="a.Value" ng-required="a.Required" />
            <span ng-show="profileAttributesForm['attribute_{{$index}}'].$invalid">Enter a Text</span> text : {{a.Value}}
        </div>

        <div ng-if="a.DataType == 4">
            <div combo list-id="a.LookUpList" name="attribute_{{$index}}" selected-item="a.Value" ng-required="a.Required"></div>
            <span ng-show="profileAttributesForm['attribute_{{$index}}'].$invalid">lookup Required</span> Value from lookup: {{a.Value}}
        </div>
    </div>
</form>

Sample of attributes ($scope.attributes) which is iterated over in the form, I am providing it just for illustration purposes:

[{
    "AttributeID": 1,
    "DataType": 4,
    "NodeID": 0,
    "Name": "Name",
    "Description": null,
    "LookUpList": 1,
    "SortAscending": false,
    "Required": true,
    "DefaultValue": "1",
    "Order": 1,
    "Value": ""
}, {
    "AttributeID": 3,
    "DataType": 1,
    "NodeID": 0,
    "Name": "Job Title",
    "Description": null,
    "LookUpList": 0,
    "SortAscending": false,
    "Required": true,
    "DefaultValue": null,
    "Order": 2,
    "Value": ""
}, {
    "AttributeID": 4,
    "DataType": 1,
    "NodeID": 0,
    "Name": "Email",
    "Description": null,
    "LookUpList": 0,
    "SortAscending": false,
    "Required": true,
    "DefaultValue": null,
    "Order": 3,
    "Value": ""
}]

Answer

Cosmin Ababei picture Cosmin Ababei · Mar 27, 2016

In order for ngRequired to set its validator it requires ngModel to be set on the same element in order to get NgModelController from it, otherwise it will just set the required attribute on or off without affecting the parent form.

A forms state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. A controller is added automatically when an ngModel is linked inside of the form.

  • For example, this <input required type="text"> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
  • But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.

In your case, I see two solutions:

  • the simple one: move ngRequired inside combo and add it on the same element as ngModel; for this you'll also need to add a new scope variable, e.g. isRequired
  • the complex one: add require: 'ngModel' to your directive and make the appropriate changes in order for this to work. This way you'll have greater control and flexibility. For example, what will you do if, down the road, you want to add ngModelOptions to combo? If you don't implement this solution, you'll have to add it manually.

    You can start by reading What's the meaning of require: 'ngModel'? - it's an awesome question/answer which contains different examples. For a more in-depth explanation, have a look at Using NgModelController with Custom Directives. As a side note, in Angular 1.5 they improved the syntax of require - see $onInit and new "require" Object syntax in Angular components.