Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS multi-step form validation

I've followed this tutorial about AngularJS Multi-Step Form Using UI Router. The form works and I can save my data but now I'm having questions about how to validate each step in the form.

I have the following form with input fields:

Step 1

  • License Plate

Step 2

  • Name
  • Street
  • Zipcode
  • City
  • Email
  • Telephone

Step 3

  • Choose a date & time from a calendar

It looks somewhat like this:

enter image description here

I have a general base view like this:

<body ng-app="formApp">
    <div id="top"></div>
    <div class="container">
        <!-- views will be injected here -->
        <div ui-view></div>

    </div>
</body>

In my app.js I have the following (not complete, left the non important things out):

// app.js
// create our angular app and inject ngAnimate and ui-router
// =============================================================================
angular.module('formApp', ['ngAnimate', 'ui.router', 'ui.calendar'])

// configuring our routes
// =============================================================================
.config(function($stateProvider, $urlRouterProvider, $interpolateProvider) {

    $interpolateProvider.startSymbol('<%');
    $interpolateProvider.endSymbol('%>');

    $stateProvider

        // route to show our basic form (/form)
        .state('form', {
            url: '/form',
            templateUrl: 'views/form.html',
            controller: 'formController'
        })

        // nested states
        // each of these sections will have their own view
        // url will be /form/interests
        .state('form.license', {
            url: '/license',
            templateUrl: 'views/form-license.html'
        })

        // url will be nested (/form/profile)
        .state('form.profile', {
            url: '/profile',
            templateUrl: 'views/form-profile.html'
        })

        // url will be /form/payment
        .state('form.appointment', {
            url: '/appointment',
            templateUrl: 'views/form-appointment.html'
        })

        // url will be /form/success
        .state('form.success', {
            url: '/success',
            templateUrl: 'views/form-success.html'
        });

    // catch all route
    // send users to the form page
    $urlRouterProvider.otherwise('/form/license');
})

// our controller for the form
// =============================================================================
.controller('formController', function($scope, $http, $compile, $location, uiCalendarConfig) {

    $scope.formData = {};
    $scope.formData.profile = {};


    $scope.next = function(step){

        if(step == 1) 
        {

        }
        else if(step == 2)
        {

        }
    };

    // function to process the form
    $scope.processForm = function(isValid) {

    };

});

My general form.html:

<!-- form.html -->
<div class="row">
    <div class="col-sm-6 col-sm-offset-3">

        <div id="form-container">
            <form id="appointment-form" name="appointmentform" ng-submit="processForm(appointmentform.$valid)">

                <!-- our nested state views will be injected here -->
                <div id="form-views" ui-view></div>
            </form>

        </div>
    </div>
</div>

The first step in my form is in form-license.html:

<!-- form-license.html -->
<label>Nummerplaat ingeven</label>
<div class="form-group">
    <div class="col-xs-8 col-xs-offset-2">
        <input required type="text" class="form-control" name="license" ng-model="formData.license">

    </div>
</div>


<div class="form-group row">
    <div class="col-xs-4 col-xs-offset-4">
        <a ng-click="next(1)" ui-sref="form.profile" class="btn btn-next btn-block">
            Volgende
        </a>
    </div>
</div>

But now I'm wondering how I can validate this when they click on next button ... . It's not working with the normal required attribute.

Can somebody help me with this?

UPDATE:

Now I have in my first step the following:

<div class="col-xs-4 col-xs-offset-4">
    <a ng-click="next(1, processForm)" ui-sref="form.profile" ng-disabled="!licenseValidated" class="btn btn-next btn-block">
        Volgende
    </a>
</div>

In my controller:

var validateLicense = function (newVal) {
    var validated = false;
    // Run your custom validation checks
    if(newVal)
    {
        validated = true;
    }
    return validated;
};

$scope.$watch('formData.license', function (newVal) {
    $scope.licenseValidated = validateLicense(newVal);
});

Ok, that works. But in my second step I have multiple fields like this:

<div class="profile">
    <div class="form-group">
        <label class="col-sm-3 control-label" for="name">Name</label>
        <div class="col-sm-9">
            <input type="text" class="form-control" name="name" ng-model="formData.profile.name">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-3 control-label" for="street">Street</label>
        <div class="col-sm-9">
            <input type="text" class="form-control" name="street" ng-model="formData.profile.street">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-3 control-label" for="zipcode">Zipcode</label>
        <div class="col-sm-9">
            <input type="text" class="form-control" name="zipcode" ng-model="formData.profile.zipcode">
        </div>
    </div>

    <div class="form-group row">
        <div class="col-xs-8 col-xs-offset-2">
            <a ng-click="next(1)" ui-sref="form.license" class="btn btn-block btn-previous col-xs-3">
                VORIGE
            </a>
            <a ng-click="next(2)" ui-sref="form.appointment" class="btn btn-block btn-next col-xs-3">
                Volgende
            </a>
        </div>
    </div>
</div>

Do I need to create for every one of them a $scope.watch? And do I need to add them to ng-disabled of my button?

like image 436
nielsv Avatar asked Sep 24 '15 16:09

nielsv


People also ask

How are validations implemented in AngularJS?

AngularJS performs form validation on the client side. AngularJS monitors the state of the form and input fields (input, text-area, select), and notify the user about the current state. AngularJS also holds information about whether the input fields have been touched, modified, or not.

What is Ng touched?

ng-touched The field has been touched. ng-pristine The field has not been modified yet. ng-dirty The field has been modified. ng-valid The field content is valid.

How do you clear form data after submit in AngularJS?

How do you clear form data after submit in AngularJS? 1) To Remove the values in Form Fields and to reset you can use $setPristine(); $scope. formName. $setPristine();

Which of the following CSS classes are added by NG model to allow styling of form as well as controls?

To allow styling of form as well as controls, ngModel adds these CSS classes: ng-valid : the model is valid. ng-invalid : the model is invalid. ng-valid-[key] : for each valid key added by $setValidity.


2 Answers

You could simply disable the next button if any of the validation steps doesn't pass.

Something like:

// Inside your controller.
// Don't overload the scope.
// Only assign what will be needed through your HTML or other AngularJS Scopes
var validateLicense = function (newVal) {
    // If you are only checking for content to be entered
    return (newVal !== '' && newVal !== undefined);
};
var validateInfo = function (newVal) {
    if (newVal.length > 0) {
        // Check to make sure that all of them have content
        for (var i = 0, l = newVal.length; i < l; i++) {
            if (newVal[i] === undefined || newVal[i] === '') {
                return false;
            }
        }
        // We didn't find invalid data, let's move on
        return true;
    }
    return false;
};

var validateDate = function (newVal) {
    var validated = false;
    // Run your custom validation checks
    return validated;
}

// Initialize the disabled "Next" buttons
$scope.licenseValidated = false;
$scope.infoValidated = false;

// Watch a single item in a form, if filled in we will let them proceed
$scope.$watch('formData.license', function (newVal) {
    $scope.licenseValidated = validateLicense(newVal);
});

// Watch a multiple items in a form, if ALL are filled in we will let them proceed
// Note that the order in this array is the order the newVal will be,
// So further validation for formData.number would be on newVal[1]
$scope.$watchGroup(['formData.name', 'formData.number', 'formData.address'], function (newVal) {
    $scope.infoValidated = validateInfo(newVal);
});

form-license.html add the ng-disabled attribute on your next button:

<a ng-click="next(1, appointmentform)" ui-sref="form.profile" class="btn btn-next btn-block" ng-disabled="!licenseValidated">
    Volgende
</a>

form-info.html repeat above steps

<a ng-click="next(1, appointmentform)" ui-sref="form.profile" class="btn btn-next btn-block" ng-disabled="!infoValidated">
    Volgende
</a>

And so on...

See this Fiddle for Demo

like image 80
jnthnjns Avatar answered Sep 23 '22 12:09

jnthnjns


You have a couple of options available to you depending on how you want to approach it.

To start, you should use ng-form for each of the 3 form steps. This will allow you to validate each individually without having to worry about the other sections.

So as an example your first form step might turn into:

<ng-form name="LicenseForm">
    <label>Nummerplaat ingeven</label>
    <div class="form-group">
        <div class="col-xs-8 col-xs-offset-2">
            <input required type="text" class="form-control" name="license" ng-model="formData.license">

        </div>
    </div>


    <div class="form-group row">
        <div class="col-xs-4 col-xs-offset-4">
            <a ng-click="next(1, LicenseForm)" ui-sref="form.profile" class="btn btn-next btn-block">
                Volgende
            </a>
        </div>
    </div>
</ng-form>

This gives you access to the form validation properties for just this step. At this point you can update your controller to use .$invalid or .$valid on the form object that is now being passed in the next submit button, meaning you can now do something like:

$scope.next = function(step, form) {
    if (form.$invalid) {
        console.log('Form is invalid!');
        return;
    }
    // move to next section
    ...
};
like image 39
csbarnes Avatar answered Sep 23 '22 12:09

csbarnes