Series 2: Build an Angular 1 Multi-Step Wizard using UI-Router 1.0 – Part 1
This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 2.
This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 2. You will learn how to build a multi-step wizard using the following technologies:
The source code for this tutorial series is published on GitHub. Demo application is hosted in Microsoft Azure.
Part 1: Create a SPA in UI-Router 1.0 with Angular 1.5+ component-based architecture
In the previous tutorial series, we created the Multi-Step Wizard in UI-Router legacy v 0.x for Angular 1. In this tutorial, we will continue to use Visual Studio Code to build the initial structure of the Single-Page Application (SPA) using UI-Router 1.0 with Angular 1.5+ component-based architecture.
Client-Side Technologies
- AngularJS 1.6.1
- Angular 1.5+ Components
- UI-Router 1.0.0-rc.1 for Angular 1
- UI-Router Guide: Route to Component
- Circular Bootstrap tabs Snippet by riliwanrabo
- Bootstrap
- Font Awesome
The application structure, styles, and patterns follow the recommendations outlined in John Papa’s Angular 1 Style Guide. The application idea is inspired by Scotch.io’s tutorial.
Task 1. Set up the Project Structure
I prefer to organize my project based on features/modules. Building a maintainable, scalable, and well-organized application should start from the beginning. Using DRY and SRP patterns, we will create smaller code files that each handle one specific job. This approach has helped me personally to locate, enhance, and debug requested features from clients quickly.
Let’s creating folders and copying files to C:\_tutorials\ng-multi-step-wizard-ui-router1 from GitHub as listed below:
ng-multi-step-wizard-ui-router1 // Project folder |--app |--address // Address feature |--form // Form feature |--personal // Personal feature |--result // Result feature |--work // Work feature |--content |--css |--riliwan-rabo.css |--style.css |--images |--favicon.ico
Task 2. Create startup page
-
Add the index.html file to the ng-multi-step-wizard-ui-router1 folder.
-
Replace the code in this file with the following:
<!DOCTYPE html> <html lang="en" ng-app="wizardApp"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <title>Multi-Step Wizard using AngularJS 1.5+ And UI-Router 1.0 by Cathy Wun</title> <!-- CSS Files --> <link rel="icon" type="image/png" href="content/images/favicon.ico" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.css" /> <link rel="stylesheet" href="content/css/riliwan-rabo.css" /> <link rel="stylesheet" href="content/css/style.css" /> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <!-- views will be injected here --> <section style="background:#efefe9;"> <div class="container"> <div ui-view></div> </div> </section> <!-- Vendor js libraries --> <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-rc.1/angular-ui-router.min.js"></script> <!-- Bootstrapping --> <script src="app/app.module.js"></script> <!-- Wizard - Form feature --> <script src="app/form/form.component.js"></script> <script src="app/form/form.controller.js"></script> <script src="app/form/formData.value.js"></script> <!-- Wizard - Personal feature --> <script src="app/personal/personal.component.js"></script> <script src="app/personal/personal.controller.js"></script> <!-- Wizard - Work feature --> <script src="app/work/work.component.js"></script> <script src="app/work/work.controller.js"></script> <!-- Wizard - Address feature --> <script src="app/address/address.component.js"></script> <script src="app/address/address.controller.js"></script> <!-- Wizard - Result feature --> <script src="app/result/result.component.js"></script> <script src="app/result/result.controller.js"></script> </body> </html>
The index.html file serve as our startup page. It loads all our resources (.css and .js) and adds a
ui-view
directive ofUI-Router
to inject our views. It also usesng-app
directive to designatewizardApp
as the root module of our Angular application.
Task 3. Create our application and routes
The app.module.js file creates our angular app and configures state-based routes. It is defined inside our root module named wizardApp
,
-
Add the app.module.js file to the app folder.
-
Replace the code in this file with the following:
(function() { 'use strict'; // Creating our angular app and inject ui-router // ============================================================================= var app = angular.module('wizardApp', ['ui.router']) // Configuring our states // ============================================================================= app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { // For any unmatched url, redirect to /wizard/personal $urlRouterProvider.otherwise('/form/personal'); $stateProvider // PARENT STATE: form state .state('form', { url: '/form', component: 'formComponent' }) // NESTED STATES: child states of 'form' state // URL will become '/form/personal' .state('form.personal', { url: '/personal', component: 'personalComponent' }) // URL will become /form/work .state('form.work', { url: '/work', component: 'workComponent' }) // URL will become /form/address .state('form.address', { url: '/address', component: 'addressComponent' }) // URL will become /form/result .state('form.result', { url: '/result', component: 'resultComponent' }) } ]); })();
Line 6 is loading our app-specific module (
ui.router
) in the app. -
Lines 19 – 47 have our state-based routes created. Each state has its own
url
andcomponent
.The
form
state is the parent state to the following child states:form.personal
form.work
form.address
form.result
When one of the child states is active, the parent state is implicitly active as well. Child state will route to its component. The child component is placed inside its parent’s
ui-view
.
Task 4. Create the Form feature
The Form
feature contains the following files:
- form.component.js: is a component controls the view, the controller, and input data for the
Form
- formData.value.js: contains input data
- form.controller.js: is a controller contains the properties and functions that are bound to the
Form
view. It also contains the presentation logic for theForm
view, and is the glue between the data and the view. - form.html: is an AngularJS HTML template that defines the view for the
Form
Task 5. Add component to the Form feature
-
Add the form.component.js file to the form folder.
-
Replace the code in this file with the following:
(function () { 'use strict'; angular .module('wizardApp') .component('formComponent', { templateUrl: 'app/form/form.html', controller: 'FormController', controllerAs: 'vm' }) })();
-
The
FormComponent
is defined inside our root module namedwizardApp
. It has its own templateUrl (view file) and controller.
Task 6. Add data model to the Form feature
-
Add the formData.value.js to the form folder.
-
Replace the code in this file with the following:
(function () { 'use strict'; angular .module('wizardApp') .value('FormDataModel', FormDataModel); function FormDataModel() { this.firstName = ''; this.lastName = ''; this.email = ''; this.work = 'Code'; this.street = ''; this.city = ''; this.state = ''; this.zip = ''; } })();
The
FormDataModel
is defined inside a root module namedwizardApp
. It contains all input data.
Task 7. Add controller and view to the Form feature
-
Add the form.controller.js file to the form folder.
-
Replace the code in this file with the following:
(function () { 'use strict'; angular .module('wizardApp') .controller('FormController', FormController); FormController.$inject = ['FormDataModel']; function FormController(FormDataModel) { var vm = this; vm.title = 'Multi-Step Wizard'; // we will store all of our form data in this object vm.formData = new FormDataModel(); vm.$onInit = activate; vm.getData = getData; //////////////// function activate() { console.log(vm.title + ' loaded!'); } function getData() { return vm.formData; } } })();
The
FormController
is defined inside our root module namedwizardApp
. We will store our data intoformData
object. After the form feature has been loaded successfully, we use theconsole.log()
method to write Multi-Step Wizard loaded! into the browser console. To see the result, activate the browser console with F12, and select Console in the menu. -
Add the form.html file to the form folder.
-
Replace the code in this file with the following:
<div class="row"> <div class="board"> <!-- Circular Tab Area --> <div class="board-inner" id="status-buttons"> <ul class="nav nav-tabs" id="myTab"> <div class="liner"></div> <!-- circular user icon --> <li> <a ui-sref-active="active" ui-sref=".personal" data-toggle="tab" title="personal"> <span class="round-tabs one"> </span> </a> </li> <!-- circular tasks icon --> <li> <a ui-sref-active="active" ui-sref=".work" data-toggle="tab" title="work"> <span class="round-tabs two"> </span> </a> </li> <!-- circular home icon --> <li> <a ui-sref-active="active" ui-sref=".address" data-toggle="tab" title="address"> <span class="round-tabs three"> </span> </a> </li> <!-- circular ok icon --> <li> <a ui-sref-active="active" ui-sref=".result" data-toggle="tab" title="result"> <span class="round-tabs four"> </span> </a> </li> </ul> <div class="clearfix"></div> </div> <!-- End Circular Tab Area --> <!-- Content Area --> <div class="tab-content"> <!-- Nested view --> <div ui-view></div> </div> <!-- End Content Area --> </div> <!-- For Debugging: show our formData as it is being typed --> <pre>{{ vm.formData | json }}</pre> </div>
The form.html uses Bootstrap for quick styling and building responsive layouts. It also provides circular tab menus to navigate to different view. To highlight active circular tab menus, we add
ui-sref-active
directive ofUI-Router
to them. The active class will be activated if the current state matches the state inui-sref
. -
We have the following circular icons in tab menu:
user
tasks
home
ok
-
Lines 9-15 assign the circular
user
icon to thePersonal
tab. When you click theuser
icon in the tab menu, thePersonal
view will be placed in the content area of theform
view: -
Lines 18-24 assign the circular
tasks
icon to theWork
tab. When you click the circulartasks
icon in the tab menu, theWork
view will be placed in the content area of theform
view: -
Lines 27-33 assign the circular
home
icon to theAddress
tab. When you click the circularhome
icon in the tab menu, theAddress
view will be placed in the content area of theform
view: -
Lines 36-42 assign the circular
ok
icon to theResult
tab. When you click the circularok
icon in the tab menu, theResult
view will be placed in the content area of theform
view: -
Line 58 will always display our
formData
object in real time.
In the next tutorial, we will build the Personal, Work, Address, and Result Features using UI-Router 1.0 with Angular 1.5+ component-based architecture.