20 Jan 2015, 04:00

AngularJS: Form Validation

A common scenario when validating form input is to call back to the server to check some detail or other before the final submission. For example, where the user has been asked to select a username, we might choose to verify that the username is available ahead of time.

Rather than creating scope variables to keep track of whether or not a form is valid, we are better off using the built-in validation facility that AngularJS provides out of the box!

One powerful feature is the ability to set custom error conditions on a form field (in additon to minlength, required etc.). The following code snippets provide an example of how this can be used:

HTML

<form name="form" novalidate ng-submit="ConfirmAccount()">
	<p ng-show="form.$dirty && form.username.$error.conflict">
		Username is not available. Please try another one.
	</p>
	<input type="text" ng-focus="form.$setPristine()" name="username" ng-model="confirm.username">
</form>

form.username.$error.conflict will exist and be set to true by our controller once the username check has been performed.

Javascript

$scope.ConfirmAccount = function() {
	checkUserAvailable(user.username).then(function(result) {
		$scope.form.username.$setValidity("conflict", result.usernameAvailable);
		if( $scope.form.$invalid ) { return; }

		// proceed with form logic
	}
};

If the server returns result.usernameAvailable == false then the validity of form.username will be false, and our error message will be displayed. The "conflict" key is an aribtary label that I’ve chosen to indicate when the username is already taken, but you can choose anything you like.

26 Mar 2014, 06:33

AngularJS + Martini: html5mode

By default AngularJS displays URL paths prefixed with a # symbol. This enables backwards compatibility with browsers that don’t support HTML5 history API. The AngularJS guide explains this in detail here

To remove the # symbol and display more normal-looking URLs requires the use of html5mode in AngularJS. This is enabled via $locationProvider, for example:

app.config(['$routeProvider', '$locationProvider',
       function($routeProvider, $locationProvider) {

           $locationProvider.html5Mode(true).hashPrefix('!');

           $routeProvider.
               when('/signin', {
               templateUrl: 'components/signin.html',
               controller: 'SigninCtrl'
           }). 
               when('/', {
               templateUrl: 'components/home.html',
               controller: 'HomeCtrl'
           }). 
               otherwise({
               redirectTo: '/' 
           }); 
       }   
]);

If the client starts browsing at / then this will work fine. The webserver sends index.html to the client and AngularJS can load itself up ready to handle the other routes itself. A problem occurs if the client starts browsing directly to a path that the webserver may not know about, for example /signin. When the web server receives that request it will return a HTTP 404 error. What we need to do instead is tell our server to serve the contents of index.html. Note, we are not performing a HTTP redirect here.

I’m using Go to serve my static content, and using the Martini web framework to make life a bit easier.

To have Martini to do the necessary rewrite, perform the following setup:

		router := martini.NewRouter()
        router.NotFound(func(w http.ResponseWriter, r *http.Request) {
                // Only rewrite paths *not* containing filenames
                if path.Ext(r.URL.Path) == "" {
                        http.ServeFile(w, r, "public/index.html")
                } else {
                        w.WriteHeader(http.StatusNotFound)
                        w.Write([]byte("404 page not found"))
                }
        })
        m := martini.New()
        m.Use(martini.Logger())
        m.Use(martini.Recovery())
        m.Use(martini.Static("public"))
        m.Action(router.Handle)

Change public to suit whichever folder your web content lives in.

11 Mar 2014, 04:05

Angularjs: form data

There are several possible ways to submit form data to a web server:

  • urlencoded parameter string appended to the URL e.g ?param1=foo&param2=bar
  • urlencoded parameter string contained in the body of the request, and header set to Content-Type: application/x-www-form-urlencoded
  • multipart form data contained in the body of the request, and header set to Content-Type: multipart/form-data; boundary=...
  • JSON encoded string contained in the body of the request, and header set to Content-Type: application/json;charset=utf-8

The last one is the method that AngularJS’s $http service uses by default when POSTing data payloads to the server. To override that you need to be a bit more explicit when calling $http e.g.

$http({
    url: "/signin",
    method: 'POST',
    data: encodeURIComponent("username="+username+"&password="+password),
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function () { console.log("Success"); }, 
         function () { console.log("Failure"); }
);

Note the use of the builtin Javascript function encodeURIComponent to encode the string.