This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 3. 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 for Angular 2
In the previous tutorial series, we created the Multi-Step Wizard using UI-Router v 1.0 with Angular 1.5+ component-based architecture. In this tutorial, we will continue to use Visual Studio Code to build the initial structure of the Single-Page Application (SPA) using using UI-Router v 1.0, Angular 2, and TypeScript 2.0+.
Client-Side Technologies
The application structure, styles, and patterns follow the recommendations outlined in the official Angular 2 Style Guide. The application idea is inspired by Scotch.io’s tutorial.
I use the official Angular 2 Developer Guide and UI-Router for Angular 2 – Hello Solar System! as a reference to write this tutorial.
Prerequisites
The following are required to complete this tutorial:
- Build the QuickStart app if you are new to Angular 2
- Install Node.js and npm (Nods.js > 6.3.x and npm > 3.10.x) on your machine if you haven’t done so already
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\ng2-multi-step-wizard-ui-router1 from GitHub as listed below:
ng2-multi-step-wizard-ui-router1
|--app
|--address
|--data
|--navbar
|--personal
|--result
|--work
|--content
|--css
|--loading-bars.css
|--riliwan-rabo.css
|--style.css
|--images
|--favicon.ico
|--loading-bars.svg
|--js
|--loading-bars.js
|--package.json
|--systemjs.config.js
|--tsconfig.json
-
Lines 19-21 list the package definition and configuration files:
- package.json: defines scripts and serves as documentation for what packages the Employee Tracker depends on.
- systemjs.config.js: loads application and library modules.
- tsconfig.json: is a configuration file to guide the TypeScript compiler as it generates JavaScript files.
Task 2. Install the Packages
Open the terminal window, enter the npm install
command to install all listed packages and libraries in package.json using npm
.
Task 3. Create startup page
-
Add the index.html file to the ng2-multi-step-wizard-ui-router1 folder.
-
Replace the code in this file with the following:
<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>Multi-Step Wizard using Angular 2 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" />
<link rel="stylesheet" href="content/css/loading-bars.css" />
<!-- IE required polyfills, in this exact order -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<multi-step-wizard-app>
<!-- Show simple splash screen-->
<div class="splash">
<div class="color-line"></div>
<div class="splash-title">
<h1>Angular 2 Multi-Step Wizard</h1>
<img src="content/images/loading-bars.svg" width="64" height="64" title="" alt="" />
</div>
</div>
</div>
</multi-step-wizard-app>
</body>
</html>
The index.html file serve as our startup page. It performs the following functions:
- loads our resources (.css and .js)
- configures
SystemJS
to load library modules and launch our application by running the AppModule
in the main.ts file - renders our application’s component between the
multi-step-wizard-app
tags - shows our splash screen
Task 4. Bootstrap our application
-
Add the main.ts file to the app folder.
-
Replace the code in this file with the following:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Line 9 bootstraps the imported AppModule
from app.module.ts file using the Just in Time
(JIT) complier to launch the application.
Task 5. Create the root module
By convention, every Angular app has a root module class called AppModule
in app.module.ts file. The @NgModule decorator allows us to bundle components, services, pipes, and directives at the module level.
-
Let’s add app.module.ts file to the app folder.
-
Replace the code in this file with the following:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UIRouterModule } from "ui-router-ng2";
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { NavbarComponent } from './navbar/navbar.component';
import { PersonalComponent } from './personal/personal.component';
import { WorkComponent } from './work/work.component';
import { AddressComponent } from './address/address.component';
import { ResultComponent } from './result/result.component';
import { UIRouterConfigFn } from "./app.router";
import { appStates } from "./app.states";
import { FormDataService } from './data/formData.service'
@NgModule({
imports: [ BrowserModule,
FormsModule,
UIRouterModule.forRoot({
states: appStates,
useHash: true,
config: UIRouterConfigFn
})
],
providers: [{ provide: FormDataService, useClass: FormDataService }],
declarations: [ AppComponent, NavbarComponent, PersonalComponent, WorkComponent, AddressComponent, ResultComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
-
Lines 24 – 31 import the following supporting modules whose exported components, directives, or pipes are referenced by the component declared in the AppModule
.
BrowserModule
: provides critical services that are essential to launch and run our app in the browser FormsModule
: provides critical services and functionalities to create form UIRouterModule
: is a UIRouter module which provides the application-wide configured services with routes in the root module
-
Line 32 registers the FormDataService
with the injector in the root module so that the same instance of the FormDataService
is available to all part of the application.
-
Line 33 declares a list of components (AppComponent
, NavbarComponent
, PersonalComponent
, WorkComponent
, AddressComponent
, ResultComponent
) that belong to the AppModule
.
-
Line 34 bootstraps the root component named AppComponent
when Angular starts the application.
-
Line 37 exports the AppModule
so that the main.ts file can import it.
Task 6. Configure routes for the Router
The application will have one Router which enables navigation from one view to the next. To learn more about Routing & Navigation
in general, please see the official doc here.
-
Add the app.states.ts file to the app folder.
-
Replace the code in this file with the following:
import { PersonalComponent } from './personal/personal.component';
import { WorkComponent } from './work/work.component';
import { AddressComponent } from './address/address.component';
import { ResultComponent } from './result/result.component';
export const appStates = [
{ name: 'personal', url: '/personal', component: PersonalComponent },
{ name: 'work', url: '/work', component: WorkComponent },
{ name: 'address', url: '/address', component: AddressComponent },
{ name: 'result', url: '/result', component: ResultComponent }
];
We configure our Router with four states:
- 1st State: when URL matches the path segment
/personal
, activate the personal
state, route to the PersonalComponent
- 2nd Route: when URL matches the path segment
/work
, activate the work
state, route to the WorkComponent
- 3rd State: when URL matches the path segment
/address
, activate the address
state, route to the AddressComponent
- 4th Route: when URL matches the path segment
/result
, activate the result
state, route to the ResultComponent
-
Add the app.router.ts file to the app folder.
-
Replace the code in this file with the following:
import { UIRouter } from "ui-router-ng2";
export function UIRouterConfigFn(router: UIRouter) {
router.urlService.rules.otherwise({ state: 'personal' });
}
The constructor function of the UIRouterConfig
expects an injectable parameters named UIRouter
. When URL doesn’t match any routes defined in our configuration, route to the personal
state by default.
NOTE: We passed our appStates
configuration, and UIRouterConfigFn
function into the UIRouterModule.forRoot
method which returns a module containing the configured Router. We registered this configured Router with the root module named AppModule
.
Task 7. Create our root component and its view
-
Add the root component named app.component.ts file to the app folder.
-
Replace the code in this file with the following:
import { Component, OnInit, Input } from '@angular/core';
import { UIROUTER_DIRECTIVES } from "ui-router-ng2";
import { FormDataService } from 'app/data/formData.service'
@Component ({
selector: 'multi-step-wizard-app'
,directives: [ UIROUTER_DIRECTIVES ]
,templateUrl: 'app/app.component.html'
})
export class AppComponent implements OnInit {
title = 'Multi-Step Wizard';
@Input() formData;
constructor(private formDataService: FormDataService) {
}
ngOnInit() {
this.formData = this.formDataService.getData();
console.log(this.title + ' loaded!');
}
}
The AppComponent
is our application shell. It imports the FormDataService
as a service. Its constructor function expects an injectable parameter named FormDataService
. When our AppComponent
activates, we call our FormDataService.getData
method to initialize formData
with the original input data.
-
Add the view named app.component.html file to the app folder.
-
Replace the code in this file with the following:
<section style="background:#efefe9;">
<div class="container">
<div class="board">
<!-- Navigation Area (Circular Tabs) -->
<msw-navbar></msw-navbar>
<!-- End Navigation Area (Circular Tabs) -->
<!-- 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>{{ formData | json }}</pre>
</div>
</section>
The app.component.html file contains the master layout for our HTML. It provides a shell
with two regions: a navigation area and a content area. The navigation area renders the Navbar
view between the msw-navbar
tags. The content area uses ui-view
directive to display the views produced by the UIRouter. In other words, when you click a circular icon in the tab menu, it’s corresponding view is loaded in the content area.
-
Line 17 will always display our formData
object in real time.
Task 8. Create the Navbar Feature
The Navbar
feature contains the following files:
- navbar.component.ts: is a component contains the properties and functions that are bound to the
Navbar
view. It also contains the presentation logic for the Navbar
view, and is the glue between the data and the view. - navbar.component.html: is an Angular 2 HTML template that defines the view for the
Navbar
Task 9. Add component and view to the Navbar feature
-
Add the navbar.component.ts file to the navbar folder.
-
Replace the code in this file with the following:
import { Component } from '@angular/core';
@Component ({
selector: 'msw-navbar'
,templateUrl: 'app/navbar/navbar.component.html'
})
export class NavbarComponent {}
The navigation area of the AppComponent
uses msw-navbar
to display the Navbar
view.
-
Add the view named navbar.component.html file to the navbar folder.
-
Replace the code in this file with the following:
<div class="board-inner" id="status-buttons">
<ul class="nav nav-tabs" id="myTab">
<div class="liner"></div>
<!-- circular user icon -->
<li>
<a uiSrefActive="active" uiSref="personal" data-toggle="tab" title="personal">
<span class="round-tabs one">
</span>
</a>
</li>
<!-- circular tasks icon -->
<li>
<a uiSrefActive="active" uiSref="work" data-toggle="tab" title="work">
<span class="round-tabs two">
</span>
</a>
</li>
<!-- circular home icon -->
<li>
<a uiSrefActive="active" uiSref="address" data-toggle="tab" title="address">
<span class="round-tabs three">
</span>
</a>
</li>
<!-- circular ok icon -->
<li>
<a uiSrefActive="active" uiSref="result" data-toggle="tab" title="completed">
<span class="round-tabs four">
</span>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<!-- Close the Splash screen -->
<script src="content/js/loading-bars.js"></script>
The navbar.component.html uses Bootstrap for quick styling and building responsive layouts. It provides circular tab menus to navigate to different view. To highlight active circular tab menus, we add uiSrefActive
directive of UI-Router
to them. The active class will be activated if the current state matches the state in uiSref
.
-
We have the following circular icons in tab menu:
-
Lines 6-12 assign the circular user
icon to the Personal
tab. When you click the user
icon in the tab menu, the Personal
view will be placed in the content area of the form
view:
-
Lines 15-21 assign the circular tasks
icon to the Work
tab. When you click the circular tasks
icon in the tab menu, the Work
view will be placed in the content area of the form
view:
-
Lines 24-30 assign the circular home
icon to the Address
tab. When you click the circular home
icon in the tab menu, the Address
view will be placed in the content area of the form
view:
-
Lines 33-39 assign the circular ok
icon to the Result
tab. When you click the circular ok
icon in the tab menu, the Result
view will be placed in the content area of the form
view:
- Line 46 stops the “loading bars” animation after finished loading the
Navbar
view.
Task 10: Create the Data Feature
The Data
feature contains the following files:
- formData.service.ts: is a service responsible for sharing the original input data among the
PersonalComponent
, WorkComponent
, AddressComponent
, and ResultComponent
- formData.model.ts: contains input data
Task 11. Add shared service to the Data feature
-
Add the formData.service.ts file to the data folder.
-
Replace the code in this file with the following:
import { Injectable } from '@angular/core';
import { FormData } from './formData.model';
@Injectable()
export class FormDataService {
private formData: FormData = new FormData();
getData(): FormData {
return this.formData;
}
setData(formData: FormData) {
this.formData = formData;
}
}
The root module class named AppModule
imports the FormDataService
as a service. The FormDataService
shares its original input data across different components.
Task 12. Add data model to the Data feature
-
Add the formData.model.ts file to the data folder.
-
Replace the code in this file with the following:
export class FormData {
firstName: string = '';
lastName : string = '';
email: string = '';
work: string = 'Code';
street: string = '';
city: string = '';
state: string = '';
zip: string = '';
}
In the next tutorial, we will build the Personal, Work, Address, and Result Features using UI-Router 1.0 for Angular 2.
The source code for this tutorial series is published on GitHub. Demo application is hosted in Microsoft Azure.
References