AngularJS Multi-Step Form Using UI Router

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

Today we will be using AngularJS and the great UI Router and the Angular ngAnimate module to create an animated multi-step form. This technique can be used for large forms that you would like to simplify for your users. We can see this technique used in many places on the web. Places like shopping carts, signup forms, onboarding processes, and more have used the multi-step form to ease users through their online forms. Here’s what we will be building:

angular-ui-router-multi-step-form

Using UI Router and its ability to have

nested states and different views per state, we will be able to make a multi-step form fairly easily. For a quick understanding of the benefits and how UI Router works, check out our other article: AngularJS Routing Using UI-Router Let’s get down to business and start creating our awesome form!

Setting Up Our Project

We will be using a simple structure for our application. We’ll need a layout file, view files for each part of the form, a stylesheet, and our JavaScript file that will hold all of our Angular code. Here are the files for our application. Go ahead and create these files and we’ll start filling them in as we go.



- index.html
- form.html
- form-profile.html
- form-interests.html
- form-payment.html
- app.js
- style.css


Each of the form-____.html files will be used as nested views inside of the form. These are what will create each section of our form.

Our Layout/Template File index.html

Let’s start our project by creating the main file that will bring all of our resources together into one place. We will use our index.html file as our starting base. Here we will, load all our resources (AngularJS, ngAnimate, UI Router, our script and stylesheet) and place a ui-view so that we know where UI Router should inject our views. We will also be using Bootstrap for quick styling.

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <!-- CSS -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootswatch/3.1.1/darkly/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">
    
    <!-- JS -->
    <!-- load angular, nganimate, and ui-router -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-animate.min.js"></script>
    <script src="app.js"></script>
    
</head>

<!-- apply our angular app -->
<body ng-app="formApp">

    <div class="container">

        <!-- views will be injected here -->
        <div ui-view></div>

    </div>

</body>
</html>

With all of our files loaded, let’s go into our

app.js to start creating our Angular application and state-based routes. Notice how we applied our Angular app (formApp) to the body of our application.

Building Our Angular App app.js

We will create our application and routes. For larger applications, you’ll want to separate your Angular application, routes, and controllers into their own separate modules, but for our purposes, we’ll place them all together as a happy family in

app.js.

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

// configuring our routes 
// =============================================================================
.config(function($stateProvider, $urlRouterProvider) {
    
    $stateProvider
    
        // route to show our basic form (/form)
        .state('form', {
            url: '/form',
            templateUrl: 'form.html',
            controller: 'formController'
        })
        
        // nested states 
        // each of these sections will have their own view
        // url will be nested (/form/profile)
        .state('form.profile', {
            url: '/profile',
            templateUrl: 'form-profile.html'
        })
        
        // url will be /form/interests
        .state('form.interests', {
            url: '/interests',
            templateUrl: 'form-interests.html'
        })
        
        // url will be /form/payment
        .state('form.payment', {
            url: '/payment',
            templateUrl: 'form-payment.html'
        });
        
    // catch all route
    // send users to the form page 
    $urlRouterProvider.otherwise('/form/profile');
})

// our controller for the form
// =============================================================================
.controller('formController', function($scope) {
    
    // we will store all of our form data in this object
    $scope.formData = {};
    
    // function to process the form
    $scope.processForm = function() {
        alert('awesome!');
    };
    
});

We now have our application with

ngAnimate and ui.router injected. We also have our routes created. Notice how we can define a url, view file (templateUrl), and controller for each state. form will be our main state. It will also have child states denoted by the . like in form.profile. The idea behind this is that each nested state will be brought into the main form view as the state of our application changes. We’ll demonstrate this in our next section. We now have to create the view for our form and each of its nested states.

Form Template View form.html

Let’s start by creating our main form.html file. This will be used as a template file for the rest of our form view files just like our index.html was used as the overall template for our entire project. All we have to do is include a ui-view in this file so that the nested states know where to inject their view.

<!-- form.html -->
<div class="row">
<div class="col-sm-8 col-sm-offset-2">

    <div id="form-container">

        <div class="page-header text-center">
            <h2>Let's Be Friends</h2>
            
            <!-- the links to our nested states using relative paths -->
            <!-- add the active class if the state matches our ui-sref -->
            <div id="status-buttons" class="text-center">
                <a ui-sref-active="active" ui-sref=".profile"><span>1</span> Profile</a>
                <a ui-sref-active="active" ui-sref=".interests"><span>2</span> Interests</a>
                <a ui-sref-active="active" ui-sref=".payment"><span>3</span> Payment</a>
            </div>
        </div>
        
        <!-- use ng-submit to catch the form submission and use our Angular function -->
        <form id="signup-form" ng-submit="processForm()">

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

    </div>

    <!-- show our formData as it is being typed -->
    <pre>
        {{ formData }}
    </pre>


</div>
</div>

Notice how this is the second time we’ve used ui-view in this project. This is what is so great about UI Router; we are able to nest states and views. This provides us a great deal of flexibility when creating our applications. For more information about UI Router’s views, check out their official docs. Adding Active Classes Based on State We will want each of our status buttons to be able to show if they are active. In order to do this, we will use ui-sref-active that UI Router provides. This will add the class we specify if the ui-sref matches the current state. To add validation to your form, also read: AngularJS Form Validation By now you’re probably wondering what our form looks like. Let’s go into our browser and take a look!

angular-multi-step-form-ui-router-basic

Getting there. Not really everything we had hoped for, but it has the beginnings of something great. Let’s keep pushing forward. Let’s add a bit of stylilng and then we’ll add our nested views and animations.

Basic Styling style.css

We are going to style our form-container and the status-buttons so that we get a better looking form.

/* style.css */
/* BASIC STYLINGS
============================================================================= */
body                            { padding-top:20px; }

/* form styling */
#form-container                { background:#2f2f2f; margin-bottom:20px;
    border-radius:5px; }
#form-container .page-header   { background:#151515; margin:0; padding:30px; 
    border-top-left-radius:5px; border-top-right-radius:5px; }

/* numbered buttons */
#status-buttons                 {  }
#status-buttons a               { color:#FFF; display:inline-block; font-size:12px; margin-right:10px; text-align:center; text-transform:uppercase; }
#status-buttons a:hover         { text-decoration:none; }

/* we will style the span as the circled number */
#status-buttons span            { background:#080808; display:block; height:30px; margin:0 auto 10px; padding-top:5px; width:30px; 
    border-radius:50%; }

/* active buttons turn light green-blue*/
#status-buttons a.active span   { background:#00BC8C; }

Now our buttons will look better and be more in line with what we want. Let’s move on to the nested views.

Nested Form Views form-profile.html, form-interests.html, form-payment.html

This will be the easy part. We’ll define our different views with the input fields that we need. We’ll also bind them to our formData object so that we can see our data getting built as we type into our form. Here are our view files that are used in our nested states:

Form Profile View

<!-- form-profile.html -->
<div class="form-group">
    <label for="name">Name</label>
    <input type="text" class="form-control" name="name" ng-model="formData.name">
</div>

<div class="form-group">
    <label for="email">Email</label>
    <input type="text" class="form-control" name="email" ng-model="formData.email">
</div>

<div class="form-group row">
<div class="col-xs-6 col-xs-offset-3">
    <a ui-sref="form.interests" class="btn btn-block btn-info">
    Next Section <span class="glyphicon glyphicon-circle-arrow-right"></span>
    </a>
</div>
</div>

Form Interests View

<!-- form-interests.html -->
<label>What's Your Console of Choice?</label>
<div class="form-group">
    <div class="radio">
        <label>
           <input type="radio" ng-model="formData.type" value="xbox" checked>
           I like XBOX
        </label>
    </div>
    <div class="radio">
        <label>
            <input type="radio" ng-model="formData.type" value="ps">
            I like PS4
        </label>
    </div>
</div>

<div class="form-group row">
<div class="col-xs-6 col-xs-offset-3">
    <a ui-sref="form.payment" class="btn btn-block btn-info">
    Next Section <span class="glyphicon glyphicon-circle-arrow-right"></span>
    </a>
</div>
</div>

Form Payment View

<!-- form-payment.html -->
<div class="text-center">
    <span class="glyphicon glyphicon-heart"></span>
    <h3>Thanks For Your Money!</h3>
    
    <button type="submit" class="btn btn-danger">Submit</button>
</div>

Now that we have, defined these views, they will show up when we view our form. We also link to the each new state using the next button and ui-sref.

When using ui-sref, you want to link to the defined state in your routes, not the URL. Angular will then use this to build out your href for you.

Here is each page of our form now.

angular-multi-step-form-profile
angular-multi-step-form-interests
angular-multi-step-form-payment

With our pages out of the way, let’s add in our animations.

Animating Our Form Since we already loaded

ngAnimate into our project at the beginning, it has been adding the classes needed for animations as we change states. It automatically adds the classes ng-enter and ng-leave as views come and go. All we have to do now is style those and we have our finalized form! To understand Angular animations, this article is a good getting started point. Let’s jump into our CSS file, add our animations, and apply them to our form.

/* style.css */
/* ANIMATION STYLINGS
============================================================================= */
#signup-form            { position:relative; min-height:300px; overflow:hidden; padding:30px; }
#form-views             { width:auto; }

/* basic styling for entering and leaving */
/* left and right added to ensure full width */
#form-views.ng-enter,
#form-views.ng-leave      { position:absolute; left:30px; right:30px;
    transition:0.5s all ease; -moz-transition:0.5s all ease; -webkit-transition:0.5s all ease; 
}
    
/* enter animation */
#form-views.ng-enter            { 
    -webkit-animation:slideInRight 0.5s both ease;
    -moz-animation:slideInRight 0.5s both ease;
    animation:slideInRight 0.5s both ease; 
}

/* leave animation */
#form-views.ng-leave            { 
    -webkit-animation:slideOutLeft 0.5s both ease;
    -moz-animation:slideOutLeft 0.5s both ease;
    animation:slideOutLeft 0.5s both ease;   
}

/* ANIMATIONS
============================================================================= */
/* slide out to the left */
@keyframes slideOutLeft {
    to      { transform: translateX(-200%); }
}
@-moz-keyframes slideOutLeft {  
    to      { -moz-transform: translateX(-200%); }
}
@-webkit-keyframes slideOutLeft {
    to      { -webkit-transform: translateX(-200%); }
}

/* slide in from the right */
@keyframes slideInRight {
    from    { transform:translateX(200%); }
    to      { transform: translateX(0); }
}
@-moz-keyframes slideInRight {
    from    { -moz-transform:translateX(200%); }
    to      { -moz-transform: translateX(0); }
}
@-webkit-keyframes slideInRight {
    from    { -webkit-transform:translateX(200%); }
    to      { -webkit-transform: translateX(0); }
}

First, we style our form so that when a view leaves or enters, they are positioned absolutely. This ensures that one view doesn’t push the other view down when entering. Second, we apply our animations to the

.ng-enter and .ng-leave classes. Third, we define the animations using @keyframes. With all those parts working together, we have our full form that has Angular animations, UI Router based states, and Angular data-binding.

Conclusion

There are many great ways that UI-Router, ngAnimate, and all the fun stuff in Angular can come together to create great applications. Hopefully this article has shown you how you can take a multi-step form and use many Angular techniques and tools to build it. These concepts here can be applied to many other user interfaces, especially when being creative and letting your imagination run wild. Thanks for reading and as always, sound off in the comments with any questions.

Chris Sevilleja

Co-founder of Scotch.io. Slapping the keyboard until something good happens.