Andrew Gray
Andrew Gray

Reputation: 3790

How would one write an Angular directive for a 'Required If' scenario?

I'm working on understanding how to use Angular Directives to implement front-end validations. While I'm familiar with the way that directives generally work, what I'm having a hard time finding in any tutorial, blogpost, or even the actual Angular docs, is how to meaningfully implement a useful validation. (By that, I mean one that isn't already there, as in this basic tutorial for how to implement a validation attribute.

The most common of these in my experience, is a 'Required If' scenario. If I have Text Field A and Text Field B in my form, and Text Field A has a value, my business rules tell me that Text Field B must have a value. However, the various tutorials for validation directives have all only relied on the element that they are tied to.

Question: I suspect that I am approaching the problem of how to implement something like a Required If validation completely wrong. In the Angular way, what is the correct way to require a value in a form if and only if a field that it's dependent on has a value?

Upvotes: 1

Views: 191

Answers (2)

Andrew Gray
Andrew Gray

Reputation: 3790

I did some additional experimentation today. A coworker prompted me with a different situation, that finally led to me solving the problem for myself.

I was fundamentally looking at the problem in the wrong way, for starters - specifically, I was looking at it in the jQuery way, by expecting the directive to somehow expose ways to read individual form elements. This isn't necessarily correct, since we have a scope that we can evaluate.

Here is the pre-1.3 Angular directive code:

var app = angular.module('app', []);

app.controller('someController', [
    '$scope',
    function($scope) {
        $scope.valueA = '';
        $scope.valueB = 'Chicken';
    }
]);

app.directive('requiredIf', [
    function () {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attr, model) {
                // Read the companion attribute 'otherModelName'
                var otherModelName = attr.otherModelName;

                scope.$watch(attr.ngModel, function (value) {
                    var isValid = checkAgainstDependentModel(value);
                    model.$setValidity('requiredIf', isValid);
                });

                function checkAgainstDependentModel(value) {
                    // Assumes that the scope exposes a property with the passed-in
                    // model name as its name
                    var field = scope[otherModelName];

                    var isValid = true;
                    if(field != null || field != '')
                        if(value == null || value == '')
                            isValid = false;

                    return isValid;
                }
            }
        };
    }
]);

...In markup we would use it like so:

<form name='someForm'>
    <input type='text' name='mainField' ng-model='valueA' />
    <input type='text' name='subordinateField' ng-model='valueB' required-if other-model-name='valueA' />
    <span style='color=red' ng-if='someForm.subordinateField.$error.requiredIf'>
        (Required!)
    </span>
</form>

This pattern can be expanded to various other custom validations, pre-1.3. My research has shown me that Angular 1.3 will remove the $parsers and $formatters in favor of $validators and $asyncValidators.

EDIT: Instead of using $formatters/$parsers, a better idea I ran across is to instead do a scope.$watch on the associated ngModel. Since this is a pre-1.3 implementation, we can still just do model.$setValidity('thing', isValid); This is based on the answer to the question of how best to achieve remote validation pre-1.3.

Upvotes: 1

David Sung Lee
David Sung Lee

Reputation: 288

You could try using ng-if to add the next form element only if the previous form element is valid. The second element won't exist in the DOM unless the first element is valid.

Upvotes: 1

Related Questions