Mac Mac - 1 year ago 63
AngularJS Question

Angular ngMessages - Accessing Multiple Form Values

I am trying to add a custom validation function to Angular's ngMessages.

Specifically, I want the value of a few inputs (the number of inputs will be dynamic, but for now stick with 2) to total 100.

I have created a new directive called

which is triggering on a form change, but I cannot figure out how to access other form values from the
call back.

I have posted my code below. Is there something I am missing? Also, if there is a better way to accomplish this (a
function in the controller and an
, for example) please call me out.

Thanks for your help.

The form:

<!-- input box to be validated -->
<input type="number" class="form-control" name="lowBound" ng-model="ctrl.lowBound" total-one-hundred required>

<!-- validation messages -->
<div ng-messages="form['lowBound'].$error" role="alert">
<div ng-message="required">Cannot be empty</div>
<div ng-message="totalOneHundred">Sum of tasks must = 100</div>

<!-- input box to be validated -->
<input type="number" class="form-control" name="highBound" ng-model="ctrl.highBound" total-one-hundred required>

<!-- validation messages -->
<div ng-messages="form['highBound'].$error" role="alert">
<div ng-message="required">Cannot be empty</div>
<div ng-message="totalOneHundred">Sum of tasks must = 100</div>

The directive:

return {
restrict: "A",

require: ["^^form", "ngModel"],

link: function(scope, element, attributes, controllers) {

// At first, form is assigned the actual form controller...
const form = controllers[0];
const model = controllers[1];

model.$validators.totalOneHundred = function (modelValue, form, element, scope) {

// however, the value of form here is "1".
// modelValue is the value of the triggering input,
// but how can I access the other form inputs?
return true;



Answer Source

Initially I took your code and implemented this fiddle. A sum() method of the parent controller calculates the total (simple in the fiddle, but since the parent controller knows the entire, dynamic model, it is doable in the real case too). The total-one-hundred takes the sum as argument, i.e.:

<input type="number" class="form-control" name="lowBound" ng-model="ctrl.lowBound"
    total-one-hundred="ctrl.sum()" required />

Alas, it doesn't work correctly! Problem: each input displays the "Sum of tasks must = 100" error. If you change a field and the total becomes correct, that field becomes valid and stops displaying the message. But the other fields do not!

This is a conceptual problem with cross-field validations. Where does the validation belong? If you can refactor your model so what gets validated is an entire object, then you can use Angular's custom controls (described here) as in this fiddle.

Now the model looks like:

this.model = {
    lowBound: <a number>,
    highBound: <a number>

And there is an editor for the entire model, complete with its own messages:

<model-editor name="entireModel" ng-model="ctrl.model" form="form"
<div ng-messages="form['entireModel'].$error" role="alert">
  <div ng-message="totalOneHundred">Sum of tasks must = 100</div>

As you can see the total validation applies to the entire model.

The second example works correctly, if you can live with just a single message for the entire "total" validation. But I do not like it...

Angular's validation is (IMHO) a quick and dirty solution suited for simple things. Say a field must not be empty, another field must comply with a regular expression and so on. For complex things (like this case) I find it inappropriate to define business logic in the view. I prefer doing model validation and binding the validation results with Angular. To that extent, I created egkyron which is well suited for such things.