NG-Form To The Rescue

A project I’m currently work on (for a very popular apparel company) is employing AngularJS for their site. I’ve created some directives to handle collecting addresses for billing and shipping. Reusable directives with their own isolate scope. To add more awesome to the mix, I’m using ngMessage to show various error messages with the form.

This all works great, but the problem was showing the error messages correctly when there are two forms on the page.

For example, if I had a form that had two addresses in my form, it might look like this with my directives.

<form name="myForm">

<div my-address-form-directive="billing"></div>
<div my-address-form-directive="shipping"></div>

</form>

One of my directives might have template code that looks like this to show the error messages. This would show the errors when the input for city had errors.

<span class="text-danger" data-ng-messages="myForm.city.$error" data-ng-show="myForm.city.$dirty">
  <span data-ng-message="required">: this field is required</span>
  <span data-ng-message="maxlength">: Please enter only 200 characters</span>
</span>
<input name="city" ng-required="true" ng-maxlength="200" ng-model="city" />

But do you see how the myForm form name is sort of hard coded?

How to get the name of the form? 

The individual directives do not know how to get the name of the form since it’s outside the code of the directive. Hard coding would be a bad idea. Even if you can get the name, then if you had two my-address-form-directive on the page, the errors would show on each of the forms (even though they only applied to one)

One way to get the form is to require it in the directive and then assign it to the scope of the directive like this

function myDirective() {
  return {
    restrict: 'AE',
    require: '^form', // The ^ means look at the parent elements
    link: function(scope, ele, attrs, form) {
      scope.parentForm = form;
    }
  };
}

This would allow you to grab the parent form and then use that in your template like this (notice we changed myForm to parentForm to match what we’ve assigned to the scope).

<span class="text-danger" data-ng-messages="parentForm.city.$error" data-ng-show="parentForm.city.$dirty">

Double Trouble 

Now you’ve solved the problem of knowing what name the form has, but what about having two directives under the same form like my top example? The error messages would show on both directives when one of them tripped the error. Not what we want.

Most problems you encounter in your everyday coding have already been solved. Find the solution and your job is that much easier

ngForm To The Rescue 

The answer is depressingly simple. Just add a ng-form="formName" around your directive template. Like this.

<div ng-form="myForm">
  <span class="text-danger" data-ng-messages="myForm.city.$error" data-ng-show="myForm.city.$dirty">
    <span data-ng-message="required">: this field is required</span>
    <span data-ng-message="maxlength">: Please enter only 200 characters</span>
  </span>
  <input name="city" ng-required="true" ng-maxlength="200" ng-model="city" />
</div>

You can actually get rid of the code that require: ^form from the directive and its matching scope.parentForm = form. No longer needed, ngForm kills two birds with one stone.

Happy Bird Hunting.