Wednesday, December 5, 2012

Pittsburgh .NET Lightning Talks

I'll be speaking at the Pittsburgh .NET Users' Group Lightning Talks next Tuesday doing a 15 minute intro to AngularJS. I which it was a bit longer, there is a lot of cool stuff in Angular I won't get a chance to go over. Then again, given this is my first ever speaking engagement, maybe it's good it's only 15 minutes.

I'm really looking forward to the other talks as well, there are some pretty smart folks that are going to be speaking there. I hope I can keep up.

For more information on the Pittsburgh .NET User's Group, check them out on Meetup.

Monday, December 3, 2012

Angular JS: Custom Validation via Directives

Okay, for this bit on Angular, I'm going to write up a quick bit on custom validation.  I've gone over form validation before, but I think that there are still plenty of cases that Angular's default validation just doesn't cover. A lot of people's first instinct is to resort to calling controller functions to do their validation. You could do that, but that would break the really slick validation model Angular already has in place.

What you really want to do is build a directive that requires ngModel. Requiring ngModel will pass the ngModelController into the linking function as the fourth argument. The ngModel controller has a lot of handy functions on it, in particular there is $setValidity, which allows you to set the state of a model field has $valid or $invalid as well as set an $error flag.

Here is an example of a simple regex validation with a custom validation directive in JSFiddle:




Here is the code for the custom validation directive

app.directive('regexValidate', function() {
    return {
        // restrict to an attribute type.
        restrict: 'A',
        
        // element must have ng-model attribute.
        require: 'ngModel',
        
        // scope = the parent scope
        // elem = the element the directive is on
        // attr = a dictionary of attributes on the element
        // ctrl = the controller for ngModel.
        link: function(scope, elem, attr, ctrl) {
            
            //get the regex flags from the regex-validate-flags="" attribute (optional)
            var flags = attr.regexValidateFlags || '';
            
            // create the regex obj.
            var regex = new RegExp(attr.regexValidate, flags);            
                        
            // add a parser that will process each time the value is 
            // parsed into the model when the user updates it.
            ctrl.$parsers.unshift(function(value) {
                // test and set the validity after update.
                var valid = regex.test(value);
                ctrl.$setValidity('regexValidate', valid);
                
                // if it's valid, return the value to the model, 
                // otherwise return undefined.
                return valid ? value : undefined;
            });
            
            // add a formatter that will process each time the value 
            // is updated on the DOM element.
            ctrl.$formatters.unshift(function(value) {
                // validate.
                ctrl.$setValidity('regexValidate', regex.test(value));
                
                // return the value or nothing will be written to the DOM.
                return value;
            });
        }
    };
});


... and here is an implementation of our custom validation directive in markup:

<div ng-app="myApp">
    <div ng-controller="MainCtrl">
        <form name="myForm" ng-submit="doSomething()">
            <label>Must contain the word <strong>blah</strong> (case insensitive)
                <br/>
                <!-- set up the input, make sure it:
                    1. has ng-model
                    2. has a name="" so we can reference it in the model.
                    3. has regex-validate
                    4. (optional) has regex-validate-flags -->
                <input type="text" placeholder="Enter text here"
                ng-model="test" name="test" regex-validate="\bblah\b"
                regex-validate-flags="i"/>
            </label>
            <!-- set up some sort of output for validation
                    the format here is:  [formName].[fieldName].$error.[validationName]
                    where validation name is determined by 
                    ctrl.$setValidity(validationName, true/false) in your
                    custom directive.
            -->
            <span style="color:red" ng-show="myForm.test.$error.regexValidate">WRONG!</span>
        <div>
            
        <!-- for added measure, disable the submit if the form is $invalid -->
        <button type="submit" ng-disabled="myForm.$invalid">Submit</button>
        </form>
    </div>
</div>


After all is said and done, we have a reusable validation that we can now seamlessly wire up with no additional function calls that integrates into Angular's already awesome validation.  Within the custom directive itself, there are thousands of ways to skin the cat I skinned above, the possibilities are really up to the author. As long as we stick with something maintainable and testable, I think any solution anyone comes up with is perfect. I'm sure there are probably a few good Github repositories out there for angular validation directives, but it's quick and easy to make your own too. Have fun.

EDIT: I've written an entry on validation inside an ng-repeat: Validating Form Elements in a Repeat