Search code examples
javascriptangularjsangularjs-material

How do I show angularjs material validation for another model property?


Using angularjs and material, I want to be able to have a readonly textbox that displays the name for a selected object a user looks up (via a modal popup), but the textbox validation should show as required and fire off if a separate id property is not populated. Here is an example plnkr.

I was originally thinking that I could do this simply by adding a hidden field with an ng-model, name, and required attribute, it would create the associated form property for the field with required validator (which it does), and I would be able to show the validator on the readonly textbox like so:

<form name="myCtrl.myForm" novalidate>
  <input type="hidden" ng-model="myCtrl.id" name="id" required />
  <div layout="row">
    <md-input-container flex="50">
        <label>Selected Object</label>
        <input ng-model="myCtrl.selectedObject.selectedText" readonly />
        <div ng-messages="myCtrl.myForm.id.$error">
            <div ng-message="required">Please select an object.</div>
        </div>
    </md-input-container>
    <div>
        <md-button class="md-icon-button md-primary" ng-click="myCtrl.select($event)">
            <md-tooltip md-direction="top">
                Select Object
            </md-tooltip>
            <md-icon>search</md-icon>
        </md-button>
    </div>
  </div>
  <div>
    <md-button class="md-raised md-primary" type="submit">Submit</md-button>
  </div>
</form>

JS:

vm.select = function(evt) {
  // Set the selected Object
  vm.selectedObject = { selectedText: "Object id 1 selected", id: 1 };
  // Set the associated ID
  vm.id = 1;

};

However, the <div ng-message="required">Please select an object.</div> never displays when the form is submitted and validation fires. Any idea how I can accomplish this?


Solution

  • While I was typing up this question I had an idea - perhaps I should be creating a custom validator that I can apply to this input which references a separate property. That appeared to do what I needed. Here's the plnkr and here's the directive:

    angular.module('MyApp', ['ngMessages', 'ngMaterial'])
      .directive('requiredOther', RequiredOther);
    
    function RequiredOther() {
    
        return {
            require: "ngModel",
            scope: {
                requiredOtherValue: "=requiredOther"
            },
            link: function(scope, element, attributes, ngModel) {
                ngModel.$validators.requiredOther = function(modelValue) {
                    return scope.requiredOtherValue !== undefined && scope.requiredOtherValue !== null && scope.requiredOtherValue !== '';
                };
    
                scope.$watch("requiredOtherValue", function() {
                    ngModel.$validate();
                });
            }
        };
    }
    

    This is the updated HTML:

    <form name="myCtrl.myForm" novalidate>
      <input type="hidden" ng-model="myCtrl.id" />
      <div layout="row">
        <md-input-container flex="50">
            <label>Selected Object</label>
            <input name="id" ng-model="myCtrl.selectedObject.selectedText" readonly required-other="myCtrl.id" />
            <div ng-messages="myCtrl.myForm.id.$error">
                <div ng-message="requiredOther">Please select an object.</div>
            </div>
        </md-input-container>
        <div>
            <md-button class="md-icon-button md-primary" ng-click="myCtrl.select($event)">
                <md-tooltip md-direction="top">
                    Select Object
                </md-tooltip>
                <md-icon>search</md-icon>
            </md-button>
        </div>
      </div>
      <div>
        <md-button class="md-raised md-primary" type="submit">Submit</md-button>
      </div>
    </form>
    

    The required-other="myCtrl.id" directive references the id property and watches for changes and fires off validation on change:
    I guess I don't really need the hidden input field anymore either.