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
 - Telephone
 
Step 3
- Choose a date & time from a calendar
 
It looks somewhat like this:

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?
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.
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? 1) To Remove the values in Form Fields and to reset you can use $setPristine(); $scope. formName. $setPristine();
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.
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
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
    ...
};
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With