Series 3: Build an Angular 2 Multi-Step Wizard using UI-Router 1.0 and TypeScript – Part 1
This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 3.
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
- Angular 2.4.0
- SystemJS 0.19.40
- RxJS 5.0.3
- UI-Router 1.0.0-beta.4 for Angular 2
- TypeScript 2.0.10
- Circular Bootstrap tabs Snippet by riliwanrabo
- Bootstrap
- Font Awesome
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 // Project folder |--app |--address // Address feature |--data // Data feature |--navbar // Navbar feature |--personal // Personal feature |--result // Result feature |--work // Work feature |--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 theAppModule
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:
//main entry point // The browser platform with a compiler import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // The app module import { AppModule } from './app.module'; // Compile and launch the module platformBrowserDynamic().bootstrapModule(AppModule);
Line 9 bootstraps the imported
AppModule
from app.module.ts file using theJust 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'; /* App Root */ import { AppComponent } from './app.component'; import { NavbarComponent } from './navbar/navbar.component'; /* Feature Components */ import { PersonalComponent } from './personal/personal.component'; import { WorkComponent } from './work/work.component'; import { AddressComponent } from './address/address.component'; import { ResultComponent } from './result/result.component'; /* App Router */ import { UIRouterConfigFn } from "./app.router"; import { appStates } from "./app.states"; /* Shared Service */ 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 browserFormsModule
: provides critical services and functionalities to create formUIRouterModule
: 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 theFormDataService
is available to all part of the application. -
Line 33 declares a list of components (
AppComponent
,NavbarComponent
,PersonalComponent
,WorkComponent
,AddressComponent
,ResultComponent
) that belong to theAppModule
. -
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 = [ // 1st State { name: 'personal', url: '/personal', component: PersonalComponent }, // 2nd State { name: 'work', url: '/work', component: WorkComponent }, // 3rd State { name: 'address', url: '/address', component: AddressComponent }, // 4th State { name: 'result', url: '/result', component: ResultComponent } ];
We configure our Router with four states:
- 1st State: when URL matches the path segment
/personal
, activate thepersonal
state, route to thePersonalComponent
- 2nd Route: when URL matches the path segment
/work
, activate thework
state, route to theWorkComponent
- 3rd State: when URL matches the path segment
/address
, activate theaddress
state, route to theAddressComponent
- 4th Route: when URL matches the path segment
/result
, activate theresult
state, route to theResultComponent
- 1st State: when URL matches the path segment
-
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"; /** UIRouter Config */ export function UIRouterConfigFn(router: UIRouter) { // If no URL matches, go to the `personal` state's name by default router.urlService.rules.otherwise({ state: 'personal' }); }
The constructor function of the
UIRouterConfig
expects an injectable parameters namedUIRouter
. When URL doesn’t match any routes defined in our configuration, route to thepersonal
state by default.NOTE: We passed our
appStates
configuration, andUIRouterConfigFn
function into theUIRouterModule.forRoot
method which returns a module containing the configured Router. We registered this configured Router with the root module namedAppModule
.
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 theFormDataService
as a service. Its constructor function expects an injectable parameter namedFormDataService
. When ourAppComponent
activates, we call ourFormDataService.getData
method to initializeformData
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 theNavbar
view between themsw-navbar
tags. The content area usesui-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 theNavbar
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
usesmsw-navbar
to display theNavbar
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 ofUI-Router
to them. The active class will be activated if the current state matches the state inuiSref
. -
We have the following circular icons in tab menu:
user
tasks
home
ok
-
Lines 6-12 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 15-21 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 24-30 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 33-39 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 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
, andResultComponent
- 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 theFormDataService
as a service. TheFormDataService
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.